import {
  ChangeDetectorRef,
  NO_ERRORS_SCHEMA,
} from '@angular/core';
import {
  ComponentFixture,
  TestBed,
  waitForAsync,
} from '@angular/core/testing';
import {
  UntypedFormControl,
  UntypedFormGroup,
} from '@angular/forms';
import {
  ActivatedRoute,
  Router,
} from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import {
  DynamicFormControlModel,
  DynamicFormService,
} from '@ng-dynamic-forms/core';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';

import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service';
import { Bitstream } from '../../core/shared/bitstream.model';
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level';
import { Item } from '../../core/shared/item.model';
import { MetadataValueFilter } from '../../core/shared/metadata.models';
import { getEntityEditRoute } from '../../item-page/item-page-routing-paths';
import { hasValue } from '../../shared/empty.util';
import {
  INotification,
  Notification,
} from '../../shared/notifications/models/notification.model';
import { NotificationType } from '../../shared/notifications/models/notification-type';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import {
  createSuccessfulRemoteDataObject,
  createSuccessfulRemoteDataObject$,
} from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { FileSizePipe } from '../../shared/utils/file-size-pipe';
import { VarDirective } from '../../shared/utils/var.directive';
import { EditBitstreamPageComponent } from './edit-bitstream-page.component';

const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
const successNotification: INotification = new Notification('id', NotificationType.Success, 'success');

let notificationsService: NotificationsService;
let formService: DynamicFormService;
let bitstreamService: BitstreamDataService;
let primaryBitstreamService: PrimaryBitstreamService;
let bitstreamFormatService: BitstreamFormatDataService;
let dsoNameService: DSONameService;
let bitstream: Bitstream;
let bitstreamID: string;
let selectedFormat: BitstreamFormat;
let allFormats: BitstreamFormat[];
let router: Router;
let currentPrimary: string;
let differentPrimary: string;
let bundle;
let comp: EditBitstreamPageComponent;
let fixture: ComponentFixture<EditBitstreamPageComponent>;

describe('EditBitstreamPageComponent', () => {

  beforeEach(() => {
    bitstreamID = 'current-bitstream-id';
    currentPrimary = bitstreamID;
    differentPrimary = '12345-abcde-54321-edcba';

    allFormats = [
      Object.assign({
        id: '1',
        shortDescription: 'Unknown',
        description: 'Unknown format',
        supportLevel: BitstreamFormatSupportLevel.Unknown,
        mimetype: 'application/octet-stream',
        _links: {
          self: { href: 'format-selflink-1' },
        },
      }),
      Object.assign({
        id: '2',
        shortDescription: 'PNG',
        description: 'Portable Network Graphics',
        supportLevel: BitstreamFormatSupportLevel.Known,
        mimetype: 'image/png',
        _links: {
          self: { href: 'format-selflink-2' },
        },
      }),
      Object.assign({
        id: '3',
        shortDescription: 'GIF',
        description: 'Graphics Interchange Format',
        supportLevel: BitstreamFormatSupportLevel.Known,
        mimetype: 'image/gif',
        _links: {
          self: { href: 'format-selflink-3' },
        },
      }),
    ] as BitstreamFormat[];
    selectedFormat = allFormats[1];

    formService = Object.assign({
      createFormGroup: (fModel: DynamicFormControlModel[]) => {
        const controls = {};
        if (hasValue(fModel)) {
          fModel.forEach((controlModel) => {
            controls[controlModel.id] = new UntypedFormControl((controlModel as any).value);
          });
          return new UntypedFormGroup(controls);
        }
        return undefined;
      },
    });

    bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
      findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)),
    });

    notificationsService = jasmine.createSpyObj('notificationsService',
      {
        info: infoNotification,
        warning: warningNotification,
        success: successNotification,
      },
    );

    bundle = {
      _links: {
        primaryBitstream: {
          href: 'bundle-selflink',
        },
      },
      item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
        uuid: 'some-uuid',
        firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
          return undefined;
        },
      })),
    };

    const result = createSuccessfulRemoteDataObject$(bundle);
    primaryBitstreamService = jasmine.createSpyObj('PrimaryBitstreamService',
      {
        put: result,
        create: result,
        delete: result,
      });

  });

  describe('EditBitstreamPageComponent no IIIF fields', () => {

    beforeEach(waitForAsync(() => {
      bundle = {
        _links: {
          primaryBitstream: {
            href: 'bundle-selflink',
          },
        },
        item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
          uuid: 'some-uuid',
          firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
            return undefined;
          },
        })),
      };
      const bundleName = 'ORIGINAL';

      bitstream = Object.assign(new Bitstream(), {
        uuid: bitstreamID,
        id: bitstreamID,
        metadata: {
          'dc.description': [
            {
              value: 'Bitstream description',
            },
          ],
          'dc.title': [
            {
              value: 'Bitstream title',
            },
          ],
        },
        format: createSuccessfulRemoteDataObject$(selectedFormat),
        _links: {
          self: 'bitstream-selflink',
        },
        bundle: createSuccessfulRemoteDataObject$(bundle),
      });
      bitstreamService = jasmine.createSpyObj('bitstreamService', {
        findById: createSuccessfulRemoteDataObject$(bitstream),
        findByHref: createSuccessfulRemoteDataObject$(bitstream),
        update: createSuccessfulRemoteDataObject$(bitstream),
        updateFormat: createSuccessfulRemoteDataObject$(bitstream),
        commitUpdates: {},
        patch: {},
      });
      bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
        findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)),
      });
      dsoNameService = jasmine.createSpyObj('dsoNameService', {
        getName: bundleName,
      });

      TestBed.configureTestingModule({
        imports: [TranslateModule.forRoot(), RouterTestingModule, EditBitstreamPageComponent, FileSizePipe, VarDirective],
        providers: [
          { provide: NotificationsService, useValue: notificationsService },
          { provide: DynamicFormService, useValue: formService },
          {
            provide: ActivatedRoute,
            useValue: {
              data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }),
              snapshot: { queryParams: {} },
            },
          },
          { provide: BitstreamDataService, useValue: bitstreamService },
          { provide: DSONameService, useValue: dsoNameService },
          { provide: BitstreamFormatDataService, useValue: bitstreamFormatService },
          { provide: PrimaryBitstreamService, useValue: primaryBitstreamService },
          ChangeDetectorRef,
        ],
        schemas: [NO_ERRORS_SCHEMA],
      }).compileComponents();

    }));

    beforeEach(() => {
      fixture = TestBed.createComponent(EditBitstreamPageComponent);
      comp = fixture.componentInstance;
      fixture.detectChanges();
      router = TestBed.inject(Router);
      spyOn(router, 'navigate');
    });

    describe('on startup', () => {
      let rawForm;

      beforeEach(() => {
        rawForm = comp.formGroup.getRawValue();
      });

      it('should fill in the bitstream\'s title', () => {
        expect(rawForm.fileNamePrimaryContainer.fileName).toEqual(bitstream.name);
      });

      it('should fill in the bitstream\'s description', () => {
        expect(rawForm.descriptionContainer.description).toEqual(bitstream.firstMetadataValue('dc.description'));
      });

      it('should select the correct format', () => {
        expect(rawForm.formatContainer.selectedFormat).toEqual(selectedFormat.shortDescription);
      });

      it('should put the \"New Format\" input on invisible', () => {
        expect(comp.formLayout.newFormat.grid.host).toContain('invisible');
      });
      describe('when the bitstream is the primary bitstream on the bundle', () => {
        beforeEach(() => {
          (comp as any).primaryBitstreamUUID = currentPrimary;
          comp.setForm();
          rawForm = comp.formGroup.getRawValue();

        });
        it('should enable the primary bitstream toggle', () => {
          expect(rawForm.fileNamePrimaryContainer.primaryBitstream).toEqual(true);
        });
      });
      describe('when the bitstream is not the primary bitstream on the bundle', () => {
        beforeEach(() => {
          (comp as any).primaryBitstreamUUID = differentPrimary;
          comp.setForm();
          rawForm = comp.formGroup.getRawValue();
        });
        it('should disable the primary bitstream toggle', () => {
          expect(rawForm.fileNamePrimaryContainer.primaryBitstream).toEqual(false);
        });
      });
    });

    describe('when an unknown format is selected', () => {
      beforeEach(() => {
        comp.onChange({
          model: {
            id: 'selectedFormat',
            value: allFormats[0],
          },
        });
        comp.updateNewFormatLayout();
      });

      it('should remove the invisible class from the \"New Format\" input', () => {
        expect(comp.formLayout.newFormat.grid.host).not.toContain('invisible');
      });
    });

    describe('onSubmit', () => {
      describe('when the primaryBitstream changed', () => {
        describe('to the current bitstream', () => {
          beforeEach(() => {
            const rawValue = Object.assign(comp.formGroup.getRawValue(), { fileNamePrimaryContainer: { primaryBitstream: true } });
            spyOn(comp.formGroup, 'getRawValue').and.returnValue(rawValue);
          });

          describe('from a different primary bitstream', () => {
            beforeEach(() => {
              (comp as any).primaryBitstreamUUID = differentPrimary;
              comp.onSubmit();
            });

            it('should call put with the correct bitstream on the PrimaryBitstreamService', () => {
              expect(primaryBitstreamService.put).toHaveBeenCalledWith(jasmine.objectContaining({ uuid: currentPrimary }), bundle);
            });
          });

          describe('from no primary bitstream', () => {
            beforeEach(() => {
              (comp as any).primaryBitstreamUUID = null;
              comp.onSubmit();
            });

            it('should call create with the correct bitstream on the PrimaryBitstreamService', () => {
              expect(primaryBitstreamService.create).toHaveBeenCalledWith(jasmine.objectContaining({ uuid: currentPrimary }), bundle);
            });
          });
        });
        describe('to no primary bitstream', () => {
          beforeEach(() => {
            const rawValue = Object.assign(comp.formGroup.getRawValue(), { fileNamePrimaryContainer: { primaryBitstream: false } });
            spyOn(comp.formGroup, 'getRawValue').and.returnValue(rawValue);
          });

          describe('from the current bitstream', () => {
            beforeEach(() => {
              (comp as any).primaryBitstreamUUID = currentPrimary;
              comp.onSubmit();
            });

            it('should call delete on the PrimaryBitstreamService', () => {
              expect(primaryBitstreamService.delete).toHaveBeenCalledWith(jasmine.objectContaining(bundle));
            });
          });
        });
      });
      describe('when the primaryBitstream did not change', () => {
        describe('the current bitstream stayed the primary bitstream', () => {
          beforeEach(() => {
            const rawValue = Object.assign(comp.formGroup.getRawValue(), { fileNamePrimaryContainer: { primaryBitstream: true } });
            spyOn(comp.formGroup, 'getRawValue').and.returnValue(rawValue);
            (comp as any).primaryBitstreamUUID = currentPrimary;
            comp.onSubmit();
          });
          it('should not call anything on the PrimaryBitstreamService', () => {
            expect(primaryBitstreamService.put).not.toHaveBeenCalled();
            expect(primaryBitstreamService.delete).not.toHaveBeenCalled();
            expect(primaryBitstreamService.create).not.toHaveBeenCalled();
          });
        });

        describe('the bitstream was not and did not become the primary bitstream', () => {
          beforeEach(() => {
            const rawValue = Object.assign(comp.formGroup.getRawValue(), { fileNamePrimaryContainer: { primaryBitstream: false } });
            spyOn(comp.formGroup, 'getRawValue').and.returnValue(rawValue);
            (comp as any).primaryBitstreamUUID = differentPrimary;
            comp.onSubmit();
          });
          it('should not call anything on the PrimaryBitstreamService', () => {
            expect(primaryBitstreamService.put).not.toHaveBeenCalled();
            expect(primaryBitstreamService.delete).not.toHaveBeenCalled();
            expect(primaryBitstreamService.create).not.toHaveBeenCalled();
          });
        });
      });

      describe('when selected format hasn\'t changed', () => {
        beforeEach(() => {
          comp.onSubmit();
        });

        it('should call update', () => {
          expect(bitstreamService.update).toHaveBeenCalled();
        });

        it('should commit the updates', () => {
          expect(bitstreamService.commitUpdates).toHaveBeenCalled();
        });
      });

      describe('when selected format has changed', () => {
        beforeEach(() => {
          comp.onChange({
            model: {
              id: 'selectedFormat',
              value: allFormats[2],
            },
          });
          fixture.detectChanges();
          comp.onSubmit();
        });

        it('should call update', () => {
          expect(bitstreamService.update).toHaveBeenCalled();
        });

        it('should call updateFormat', () => {
          expect(bitstreamService.updateFormat).toHaveBeenCalled();
        });

        it('should commit the updates', () => {
          expect(bitstreamService.commitUpdates).toHaveBeenCalled();
        });
      });
    });
    describe('when the cancel button is clicked', () => {
      it('should call navigateToItemEditBitstreams method', () => {
        spyOn(comp, 'navigateToItemEditBitstreams');
        comp.onCancel();
        expect(comp.navigateToItemEditBitstreams).toHaveBeenCalled();
      });
    });
    describe('when navigateToItemEditBitstreams is called', () => {
      it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => {
        comp.itemId = 'some-uuid1';
        comp.navigateToItemEditBitstreams();
        expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']);
      });
    });
  });

  describe('EditBitstreamPageComponent with IIIF fields', () => {

    const bundleName = 'ORIGINAL';

    beforeEach(waitForAsync(() => {

      bitstream = Object.assign(new Bitstream(), {
        metadata: {
          'dc.description': [
            {
              value: 'Bitstream description',
            },
          ],
          'dc.title': [
            {
              value: 'Bitstream title',
            },
          ],
          'iiif.label': [
            {
              value: 'chapter one',
            },
          ],
          'iiif.toc': [
            {
              value: 'chapter one',
            },
          ],
          'iiif.image.width': [
            {
              value: '2400',
            },
          ],
          'iiif.image.height': [
            {
              value: '2800',
            },
          ],
        },
        format: createSuccessfulRemoteDataObject$(allFormats[1]),
        _links: {
          self: 'bitstream-selflink',
        },
        bundle: createSuccessfulRemoteDataObject$({
          _links: {
            primaryBitstream: {
              href: 'bundle-selflink',
            },
          },
          item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
            uuid: 'some-uuid',
            firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
              return 'True';
            },
          })),
        }),
      });
      bitstreamService = jasmine.createSpyObj('bitstreamService', {
        findById: createSuccessfulRemoteDataObject$(bitstream),
        findByHref: createSuccessfulRemoteDataObject$(bitstream),
        update: createSuccessfulRemoteDataObject$(bitstream),
        updateFormat: createSuccessfulRemoteDataObject$(bitstream),
        commitUpdates: {},
        patch: {},
      });

      dsoNameService = jasmine.createSpyObj('dsoNameService', {
        getName: bundleName,
      });

      TestBed.configureTestingModule({
        imports: [TranslateModule.forRoot(), RouterTestingModule, EditBitstreamPageComponent, FileSizePipe, VarDirective],
        providers: [
          { provide: NotificationsService, useValue: notificationsService },
          { provide: DynamicFormService, useValue: formService },
          {
            provide: ActivatedRoute,
            useValue: {
              data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }),
              snapshot: { queryParams: {} },
            },
          },
          { provide: BitstreamDataService, useValue: bitstreamService },
          { provide: DSONameService, useValue: dsoNameService },
          { provide: BitstreamFormatDataService, useValue: bitstreamFormatService },
          { provide: PrimaryBitstreamService, useValue: primaryBitstreamService },
          ChangeDetectorRef,
        ],
        schemas: [NO_ERRORS_SCHEMA],
      }).compileComponents();
    }));

    beforeEach(() => {
      fixture = TestBed.createComponent(EditBitstreamPageComponent);
      comp = fixture.componentInstance;
      fixture.detectChanges();
      router = TestBed.inject(Router);
      spyOn(router, 'navigate');
    });

    describe('on startup', () => {
      let rawForm;

      beforeEach(() => {
        rawForm = comp.formGroup.getRawValue();
      });
      it('should set isIIIF to true', () => {
        expect(comp.isIIIF).toBeTrue();
      });
      it('should fill in the iiif label', () => {
        expect(rawForm.iiifLabelContainer.iiifLabel).toEqual('chapter one');
      });
      it('should fill in the iiif toc', () => {
        expect(rawForm.iiifTocContainer.iiifToc).toEqual('chapter one');
      });
      it('should fill in the iiif width', () => {
        expect(rawForm.iiifWidthContainer.iiifWidth).toEqual('2400');
      });
      it('should fill in the iiif height', () => {
        expect(rawForm.iiifHeightContainer.iiifHeight).toEqual('2800');
      });
    });
  });

  describe('ignore OTHERCONTENT bundle', () => {

    const bundleName = 'OTHERCONTENT';

    beforeEach(waitForAsync(() => {

      bitstream = Object.assign(new Bitstream(), {
        metadata: {
          'dc.description': [
            {
              value: 'Bitstream description',
            },
          ],
          'dc.title': [
            {
              value: 'Bitstream title',
            },
          ],
          'iiif.label': [
            {
              value: 'chapter one',
            },
          ],
          'iiif.toc': [
            {
              value: 'chapter one',
            },
          ],
          'iiif.image.width': [
            {
              value: '2400',
            },
          ],
          'iiif.image.height': [
            {
              value: '2800',
            },
          ],
        },
        format: createSuccessfulRemoteDataObject$(allFormats[2]),
        _links: {
          self: 'bitstream-selflink',
        },
        bundle: createSuccessfulRemoteDataObject$({
          _links: {
            primaryBitstream: {
              href: 'bundle-selflink',
            },
          },
          item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
            uuid: 'some-uuid',
            firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
              return 'True';
            },
          })),
        }),
      });
      bitstreamService = jasmine.createSpyObj('bitstreamService', {
        findById: createSuccessfulRemoteDataObject$(bitstream),
        findByHref: createSuccessfulRemoteDataObject$(bitstream),
        update: createSuccessfulRemoteDataObject$(bitstream),
        updateFormat: createSuccessfulRemoteDataObject$(bitstream),
        commitUpdates: {},
        patch: {},
      });

      dsoNameService = jasmine.createSpyObj('dsoNameService', {
        getName: bundleName,
      });

      TestBed.configureTestingModule({
        imports: [TranslateModule.forRoot(), RouterTestingModule, EditBitstreamPageComponent, FileSizePipe, VarDirective],
        providers: [
          { provide: NotificationsService, useValue: notificationsService },
          { provide: DynamicFormService, useValue: formService },
          { provide: ActivatedRoute,
            useValue: {
              data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }),
              snapshot: { queryParams: {} },
            },
          },
          { provide: BitstreamDataService, useValue: bitstreamService },
          { provide: DSONameService, useValue: dsoNameService },
          { provide: BitstreamFormatDataService, useValue: bitstreamFormatService },
          { provide: PrimaryBitstreamService, useValue: primaryBitstreamService },
          ChangeDetectorRef,
        ],
        schemas: [NO_ERRORS_SCHEMA],
      }).compileComponents();
    }));

    beforeEach(() => {
      fixture = TestBed.createComponent(EditBitstreamPageComponent);
      comp = fixture.componentInstance;
      fixture.detectChanges();
      router = TestBed.inject(Router);
      spyOn(router, 'navigate');
    });

    describe('EditBitstreamPageComponent with IIIF fields', () => {
      let rawForm;

      beforeEach(() => {
        rawForm = comp.formGroup.getRawValue();
      });

      it('should NOT set is IIIF to true', () => {
        expect(comp.isIIIF).toBeFalse();
      });
      it('should put the \"IIIF Label\" input not to be shown', () => {
        expect(rawForm.iiifLabelContainer).toBeFalsy();
      });
    });
  });

});