import { CommonModule } from '@angular/common';
import {
  DebugElement,
  NO_ERRORS_SCHEMA,
} from '@angular/core';
import {
  ComponentFixture,
  TestBed,
  waitForAsync,
} from '@angular/core/testing';
import {
  BrowserModule,
  By,
} from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';

import { APP_DATA_SERVICES_MAP } from '../../../config/app-config.interface';
import { ArrayMoveChangeAnalyzer } from '../../core/data/array-move-change-analyzer.service';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { Item } from '../../core/shared/item.model';
import { ITEM } from '../../core/shared/item.resource-type';
import { MetadataValue } from '../../core/shared/metadata.models';
import { AlertComponent } from '../../shared/alert/alert.component';
import { BtnDisabledDirective } from '../../shared/btn-disabled.directive';
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TestDataService } from '../../shared/testing/test-data-service.mock';
import { VarDirective } from '../../shared/utils/var.directive';
import { DsoEditMetadataComponent } from './dso-edit-metadata.component';
import { DsoEditMetadataFieldValuesComponent } from './dso-edit-metadata-field-values/dso-edit-metadata-field-values.component';
import { DsoEditMetadataHeadersComponent } from './dso-edit-metadata-headers/dso-edit-metadata-headers.component';
import { DsoEditMetadataValueComponent } from './dso-edit-metadata-value/dso-edit-metadata-value.component';
import { DsoEditMetadataValueHeadersComponent } from './dso-edit-metadata-value-headers/dso-edit-metadata-value-headers.component';
import { MetadataFieldSelectorComponent } from './metadata-field-selector/metadata-field-selector.component';

const ADD_BTN = 'add';
const REINSTATE_BTN = 'reinstate';
const SAVE_BTN = 'save';
const DISCARD_BTN = 'discard';

const mockDataServiceMap: any = new Map([
  [ITEM.value, () => import('../../shared/testing/test-data-service.mock').then(m => m.TestDataService)],
]);

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

  let notificationsService: NotificationsService;

  let dso: DSpaceObject;

  beforeEach(waitForAsync(() => {
    dso = Object.assign(new Item(), {
      type: ITEM,
      metadata: {
        'dc.title': [
          Object.assign(new MetadataValue(), {
            value: 'Test Title',
            language: 'en',
            place: 0,
          }),
        ],
        'dc.subject': [
          Object.assign(new MetadataValue(), {
            value: 'Subject One',
            language: 'en',
            place: 0,
          }),
          Object.assign(new MetadataValue(), {
            value: 'Subject Two',
            language: 'en',
            place: 1,
          }),
          Object.assign(new MetadataValue(), {
            value: 'Subject Three',
            language: 'en',
            place: 2,
          }),
        ],
      },
    });

    notificationsService = jasmine.createSpyObj('notificationsService', [
      'error',
      'success',
    ]);

    TestBed.configureTestingModule({
      imports: [
        CommonModule,
        BrowserModule,
        TranslateModule.forRoot(),
        RouterTestingModule.withRoutes([]),
        DsoEditMetadataComponent,
        VarDirective,
        BtnDisabledDirective,
      ],
      providers: [
        { provide: APP_DATA_SERVICES_MAP, useValue: mockDataServiceMap },
        { provide: NotificationsService, useValue: notificationsService },
        ArrayMoveChangeAnalyzer,
        TestDataService,
      ],
      schemas: [NO_ERRORS_SCHEMA],
    })
      .overrideComponent(DsoEditMetadataComponent, {
        remove: {
          imports: [
            DsoEditMetadataValueComponent,
            DsoEditMetadataHeadersComponent,
            MetadataFieldSelectorComponent,
            DsoEditMetadataValueHeadersComponent,
            DsoEditMetadataFieldValuesComponent,
            AlertComponent,
            ThemedLoadingComponent,
          ],
        },
      })
      .compileComponents();
  }));

  beforeEach(waitForAsync(() => {
    fixture = TestBed.createComponent(DsoEditMetadataComponent);
    component = fixture.componentInstance;
    component.dso = dso;
    fixture.detectChanges();
  }));

  describe('when no changes have been made', () => {
    assertButton(ADD_BTN, true, false);
    assertButton(REINSTATE_BTN, false);
    assertButton(SAVE_BTN, true, true);
    assertButton(DISCARD_BTN, true, true);
  });

  describe('when the form contains changes', () => {
    beforeEach(() => {
      component.form.fields['dc.title'][0].newValue.value = 'Updated Title Once';
      component.form.fields['dc.title'][0].confirmChanges();
      component.form.resetReinstatable();
      component.onValueSaved();
      fixture.detectChanges();
    });

    assertButton(SAVE_BTN, true, false);
    assertButton(DISCARD_BTN, true, false);

    describe('and they were discarded', () => {
      beforeEach(() => {
        component.discard();
        fixture.detectChanges();
      });

      assertButton(REINSTATE_BTN, true, false);
      assertButton(SAVE_BTN, true, true);
      assertButton(DISCARD_BTN, false);

      describe('and a new change is made', () => {
        beforeEach(() => {
          component.form.fields['dc.title'][0].newValue.value = 'Updated Title Twice';
          component.form.fields['dc.title'][0].confirmChanges();
          component.form.resetReinstatable();
          component.onValueSaved();
          fixture.detectChanges();
        });

        assertButton(REINSTATE_BTN, false);
        assertButton(SAVE_BTN, true, false);
        assertButton(DISCARD_BTN, true, false);
      });
    });
  });

  describe('when a new value is present', () => {
    beforeEach(() => {
      component.add();
      fixture.detectChanges();
    });

    assertButton(ADD_BTN, true, true);

    it('should display a row with a field selector and metadata value', () => {
      expect(fixture.debugElement.query(By.css('ds-metadata-field-selector'))).toBeTruthy();
      expect(fixture.debugElement.query(By.css('ds-dso-edit-metadata-value'))).toBeTruthy();
    });

    describe('and gets assigned to a metadata field', () => {
      beforeEach(() => {
        component.form.newValue.newValue.value = 'New Subject';
        component.form.setMetadataField('dc.subject');
        component.form.resetReinstatable();
        component.onValueSaved();
        fixture.detectChanges();
      });

      assertButton(ADD_BTN, true, false);

      it('should not display the separate row with field selector and metadata value anymore', () => {
        expect(fixture.debugElement.query(By.css('ds-metadata-field-selector'))).toBeNull();
        expect(fixture.debugElement.query(By.css('ds-dso-edit-metadata-value'))).toBeNull();
      });
    });
  });

  function assertButton(name: string, exists: boolean, disabled: boolean = false): void {
    describe(`${name} button`, () => {
      let btn: DebugElement;

      beforeEach(() => {
        btn = fixture.debugElement.query(By.css(`#dso-${name}-btn`));
      });

      if (exists) {
        it('should exist', () => {
          expect(btn).toBeTruthy();
        });

        it(`should${disabled ? ' ' : ' not '}be disabled`, () => {
          if (disabled) {
            expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('true');
            expect(btn.nativeElement.classList.contains('disabled')).toBeTrue();
          } else {
            expect(btn.nativeElement.getAttribute('aria-disabled')).not.toBe('true');
            expect(btn.nativeElement.classList.contains('disabled')).toBeFalse();
          }
        });
      } else {
        it('should not exist', () => {
          expect(btn).toBeNull();
        });
      }
    });
  }

});