/*
 * Copyright (C) 2021 Robert Bosch GmbH Copyright (C) 2021 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { MultiFileSelectComponent } from "./multi-file-select.component";
import { FileValidationTypeError } from './file-validation-type-error';
import { BCI_MULTI_FILE_SELECT_INTL_PROVIDER } from "./multi-file-select-intl";
import { MatIconModule } from "@angular/material/icon";
import { MatTooltipModule } from '@angular/material/tooltip';
import { BciFilesizeModule } from "../filesize/filesize.module";
import { LabelValuePairComponent } from "../label-value-pair";
import { BciCardBorderDirective } from "../bci-card-border/bci-card-border.directive";
import { MatCardModule } from "@angular/material/card";

const DUMMY_FILE_NAME_WITHOUT_EXTENSION = 'test';
const DUMMY_FILE_NAME = 'test.json';
const DUMMY_FILE_CONTENT = 'test-content';
const DUMMY_FILE_TYPE = 'JSON';

const NO_FILES_ADDED_SELECTOR = '[data-test="multiFileSelect.noFilesAddedMessage"]';
const ADD_BUTTON_SELECTOR = '[data-test="multiFileSelect.addFileButton"]';
const FILES_CONTAINER_SELECTOR = '[data-test="multiFileSelect.filesContainer"]';
const FILE_TYPE_SELECTOR = '[data-test="multiFileSelect.filesContainer.fileType"]';
const FILE_NAME_SELECTOR = '[data-test="multiFileSelect.filesContainer.fileName"]';
const FILE_SIZE_SELECTOR = '[data-test="multiFileSelect.filesContainer.fileSize"]';
const REMOVE_FILE_BUTTON_SELECTOR = '[data-test="multiFileSelect.filesContainer.removeFileButton"]';

describe('MultiFileSelectComponent', () => {
  let component: MultiFileSelectComponent;
  let fixture: ComponentFixture<MultiFileSelectComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
        declarations: [ MultiFileSelectComponent, LabelValuePairComponent, BciCardBorderDirective ],
        imports: [
            MatIconModule,
            MatTooltipModule,
            BciFilesizeModule,
            MatCardModule
        ],
        providers: [BCI_MULTI_FILE_SELECT_INTL_PROVIDER]
    })
    .compileComponents();
  });

  beforeEach(() => {
      fixture = TestBed.createComponent(MultiFileSelectComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
  });

  it('should create', () => {
      expect(component).toBeTruthy();
  });

  it('component including title and addButton should be displayed', () => {
      const filecounter = fixture.nativeElement.querySelector('.filecounter');

      expect(filecounter).toBeTruthy();
      expect(getAddButtonElement()).toBeTruthy();
  });

  it('placeholder text should be shown when no file is selected', () => {
      expect(getFilesAddedContainerElement()).toBeFalsy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
  });

  it('uploaded file should be displayed as a card containing corresponding metadata', () => {
      component.maximumFiles = 6;
      jest.spyOn(component.selectedFilesChange, 'emit');
      const dummyJsonFile = createDummyJsonFile();
      const fileChangeEvent = mockFileChangeEvent(dummyJsonFile);
      component.handleFileChange(fileChangeEvent);
      fixture.detectChanges();
  
      const fileCards = getAllFileCardElements();
      const displayedFileType = fileCards[0].querySelector(FILE_TYPE_SELECTOR).textContent;
      const displayedFileName = fileCards[0].querySelector(FILE_NAME_SELECTOR).textContent;
      const displayedFileSize = fileCards[0].querySelector(FILE_SIZE_SELECTOR).textContent;
  
      expect(fileCards).toHaveLength(1);
      expect(getFilesAddedContainerElement()).toBeTruthy();
      expect(displayedFileType).toEqual(DUMMY_FILE_TYPE);
      expect(displayedFileName).toEqual(DUMMY_FILE_NAME);
      expect(displayedFileSize).toEqual(DUMMY_FILE_CONTENT.length + ' B');
      expect(component.selectedFilesChange.emit).toHaveBeenCalledTimes(1);
      expect(component.selectedFilesChange.emit).toHaveBeenLastCalledWith([dummyJsonFile]);
    });
  
    it('uploaded multiple files should be displayed as cards containing corresponding metadata', () => {
      component.maximumFiles = 6;
      jest.spyOn(component.selectedFilesChange, 'emit');
      const dummyJsonFiles = [...Array(3).keys()].map(() => createDummyJsonFileWithRandomName());
  
      dummyJsonFiles.forEach(dummyFile => {
        component.handleFileChange(mockFileChangeEvent(dummyFile));
      });
      
      fixture.detectChanges();
  
      const fileCardElements = getAllFileCardElements();
  
      expect(getFilesAddedContainerElement()).toBeTruthy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(fileCardElements).toHaveLength(3);
      for (let i = 0; i < dummyJsonFiles.length; i++) {
        expect(fileCardElements[i].querySelector(FILE_TYPE_SELECTOR).textContent).toEqual(DUMMY_FILE_TYPE);
        expect(fileCardElements[i].querySelector(FILE_NAME_SELECTOR).textContent).toEqual(dummyJsonFiles[i].name);
        expect(fileCardElements[i].querySelector(FILE_SIZE_SELECTOR).textContent).toEqual(
          DUMMY_FILE_CONTENT.length + ' B'
        );
      }
      expect(component.selectedFilesChange.emit).toHaveBeenCalledTimes(dummyJsonFiles.length);
      expect(component.selectedFilesChange.emit).toHaveBeenCalledWith([dummyJsonFiles[0], dummyJsonFiles[1], dummyJsonFiles[2]]);
    });
  
    it('file validation should fail when multiple files have the same name', () => {
      jest.spyOn(component.selectedFilesChange, 'emit');
      jest.spyOn(component.fileValidationError, 'emit');
      const dummyJsonFiles = [...Array(2).keys()].map(() => createDummyJsonFile());
  
      component.uniqueFileNames = true;
  
      dummyJsonFiles.forEach(dummyFile => {
        component.handleFileChange(mockFileChangeEvent(dummyFile));
      });
  
      fixture.detectChanges();
      const fileCardElements = getAllFileCardElements();
  
      expect(fileCardElements).toHaveLength(1);
      expect(getFilesAddedContainerElement()).toBeTruthy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(component.selectedFilesChange.emit).toHaveBeenCalledTimes(1);
      expect(component.fileValidationError.emit).toHaveBeenLastCalledWith([{
        name: DUMMY_FILE_NAME,
        validationError: FileValidationTypeError.DUPLICATED_FILE
      }]);
    });
  
    it('file validation should fail when the selected file type is not in allowedFileTypes', () => {
      component.allowedFileTypes = ['.csv'];
      jest.spyOn(component.fileValidationError, 'emit');
  
      const fileChangeEvent = mockFileChangeEvent(createDummyJsonFile());
      component.handleFileChange(fileChangeEvent);
      fixture.detectChanges();
  
      expect(getFilesAddedContainerElement()).toBeFalsy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(component.fileValidationError.emit).toHaveBeenCalledTimes(1);
      expect(component.fileValidationError.emit).toHaveBeenLastCalledWith([{
        name: DUMMY_FILE_NAME,
        validationError: FileValidationTypeError.INVALID_FILE_TYPE
      }]);
    });
  
    it('file validation should fail when the selected file has no file extension', () => {
      jest.spyOn(component.fileValidationError, 'emit');
  
      const fileChangeEvent = mockFileChangeEvent(createDummyJsonFileWithoutExtension());
      component.handleFileChange(fileChangeEvent);
      fixture.detectChanges();
  
      expect(getFilesAddedContainerElement()).toBeFalsy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(component.fileValidationError.emit).toHaveBeenCalledTimes(1);
      expect(component.fileValidationError.emit).toHaveBeenLastCalledWith([{
        name: DUMMY_FILE_NAME_WITHOUT_EXTENSION,
        validationError: FileValidationTypeError.MISSING_FILE_EXTENSION
      }]);
    });
  
    it('file validation should fail when file size exceeds maximumSizePerFileMB', () => {
      component.maximumSizePerFileMB = 0.00001;
      jest.spyOn(component.fileValidationError, 'emit');
  
      const fileChangeEvent = mockFileChangeEvent(createDummyJsonFile());
      component.handleFileChange(fileChangeEvent);
      fixture.detectChanges();
  
      expect(getFilesAddedContainerElement()).toBeFalsy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(component.fileValidationError.emit).toHaveBeenCalledTimes(1);
      expect(component.fileValidationError.emit).toHaveBeenLastCalledWith([{
        name: DUMMY_FILE_NAME,
        validationError: FileValidationTypeError.EXCEEDED_FILE_SIZE
      }]);
    });
  
    it('file validation should fail when file size exceeds maximumSizePerFileMB and the file type is not in allowedFileTypes', () => {
      component.allowedFileTypes = ['.csv'];
      component.maximumSizePerFileMB = 0.00001;
      jest.spyOn(component.fileValidationError, 'emit');
  
      const fileChangeEvent = mockFileChangeEvent(createDummyJsonFile());
      component.handleFileChange(fileChangeEvent);
      fixture.detectChanges();
  
      expect(getFilesAddedContainerElement()).toBeFalsy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(component.fileValidationError.emit).toHaveBeenCalledTimes(1);
      expect(component.fileValidationError.emit).toHaveBeenLastCalledWith([{
        name: DUMMY_FILE_NAME,
        validationError: FileValidationTypeError.EXCEEDED_FILE_SIZE_AND_INVALID_FILE_TYPE
      }]);
    });
  
    it('file validation should fail when file size exceeds maximumSizePerFileMB with configured allowedFileTypes and file has no file extension', () => {
      component.maximumSizePerFileMB = 0.00001;
      component.allowedFileTypes = ['.csv'];
      jest.spyOn(component.fileValidationError, 'emit');
  
      const fileChangeEvent = mockFileChangeEvent(createDummyJsonFileWithoutExtension());
      component.handleFileChange(fileChangeEvent);
      fixture.detectChanges();
  
      expect(getFilesAddedContainerElement()).toBeFalsy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(component.fileValidationError.emit).toHaveBeenCalledTimes(1);
      expect(component.fileValidationError.emit).toHaveBeenLastCalledWith([{
        name: DUMMY_FILE_NAME_WITHOUT_EXTENSION,
        validationError: FileValidationTypeError.EXCEEDED_FILE_SIZE_AND_INVALID_FILE_TYPE
      }]);
    });
  
    it('file validation should fail when file size exceeds maximumSizePerFileMB and the file has no file extension', () => {
      component.maximumSizePerFileMB = 0.00001;
      jest.spyOn(component.fileValidationError, 'emit');
  
      const fileChangeEvent = mockFileChangeEvent(createDummyJsonFileWithoutExtension());
      component.handleFileChange(fileChangeEvent);
      fixture.detectChanges();
  
      expect(getFilesAddedContainerElement()).toBeFalsy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(component.fileValidationError.emit).toHaveBeenCalledTimes(1);
      expect(component.fileValidationError.emit).toHaveBeenLastCalledWith([{
        name: DUMMY_FILE_NAME_WITHOUT_EXTENSION,
        validationError: FileValidationTypeError.EXCEEDED_FILE_SIZE_AND_MISSING_EXTENSION
      }]);
    });
  
    it('remove file button should remove the selected file', () => {
      jest.spyOn(component.selectedFilesChange, 'emit');
      component.selectedFiles = [createDummyJsonFile()];
      fixture.detectChanges();
  
      let fileCards = getAllFileCardElements();
      const removeFileButton = fileCards[0].querySelector(REMOVE_FILE_BUTTON_SELECTOR);
  
      expect(getFilesAddedContainerElement()).toBeTruthy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(fileCards).toHaveLength(1);
  
      removeFileButton.click();
      fixture.detectChanges();
  
      fileCards = getAllFileCardElements();
      expect(getFilesAddedContainerElement()).toBeFalsy();
      expect(getNoFilesAddedContainerElement()).toBeTruthy();
      expect(fileCards).toHaveLength(0);
      expect(component.selectedFilesChange.emit).toHaveBeenCalledTimes(1);
      expect(component.selectedFilesChange.emit).toHaveBeenLastCalledWith([]);
    });
  
    it('add file button should be disabled when selectedFiles.length >= maximumFiles', () => {
      component.selectedFiles = [createDummyJsonFile()];
      component.maximumFiles = 1;
      fixture.detectChanges();
  
      const addButton = getAddButtonElement();
      expect(addButton).toBeTruthy();
      expect(addButton.disabled).toBeTruthy();
    });
  
    it('upload multiple files at once', () => {
      jest.spyOn(component.selectedFilesChange, 'emit');
      const dummyJsonFiles = [...Array(3).keys()].map(() => createDummyJsonFileWithRandomName());
  
      component.handleFileDrop({
        // eslint-disable-next-line @typescript-eslint/no-empty-function
        stopPropagation: () => {},
        // eslint-disable-next-line @typescript-eslint/no-empty-function
        preventDefault: () => {},
        dataTransfer: {
          files: dummyJsonFiles
        }
      });
      
      fixture.detectChanges();
      const fileCardElements = getAllFileCardElements();
  
      expect(getFilesAddedContainerElement()).toBeTruthy();
      expect(fileCardElements).toHaveLength(dummyJsonFiles.length);
  
      expect(fileCardElements).toHaveLength(dummyJsonFiles.length);
      for (let i = 0; i < dummyJsonFiles.length; i++) {
        expect(fileCardElements[i].querySelector(FILE_TYPE_SELECTOR).textContent).toEqual(DUMMY_FILE_TYPE);
        expect(fileCardElements[i].querySelector(FILE_NAME_SELECTOR).textContent).toEqual(dummyJsonFiles[i].name);
        expect(fileCardElements[i].querySelector(FILE_SIZE_SELECTOR).textContent).toEqual(
          DUMMY_FILE_CONTENT.length + ' B'
        );
      }
  
      expect(component.selectedFilesChange.emit).toHaveBeenCalledTimes(1);
      expect(component.selectedFilesChange.emit).toHaveBeenCalledWith(dummyJsonFiles);
    });

  const getFilesAddedContainerElement = () => fixture.nativeElement.querySelector(FILES_CONTAINER_SELECTOR);
  const getNoFilesAddedContainerElement = () => fixture.nativeElement.querySelector(NO_FILES_ADDED_SELECTOR);
  const getAllFileCardElements = () => fixture.nativeElement.querySelectorAll('mat-card-content');
  const getAddButtonElement = () => fixture.nativeElement.querySelector(ADD_BUTTON_SELECTOR);

  const getRandomString = () => (Math.random() + 1).toString(36).substring(7);
  const createDummyJsonFileWithRandomName = (): File => new File([DUMMY_FILE_CONTENT], DUMMY_FILE_NAME_WITHOUT_EXTENSION + '-' + getRandomString() + '.json');
  const createDummyJsonFile = (): File => new File([DUMMY_FILE_CONTENT], DUMMY_FILE_NAME);
  const createDummyJsonFileWithoutExtension = (): File =>
  new File([DUMMY_FILE_CONTENT], DUMMY_FILE_NAME_WITHOUT_EXTENSION);

  const mockFileChangeEvent = (files: File): any => {
  return {
      target: {
      files: [files],
      },
  };
  };
});