import { RouterTestingModule } from '@angular/router/testing';
import { ProductImageService } from '@cdn/product-image';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainOmsService } from '@domain/oms';
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator';
import { UiCommonModule } from '@ui/common';
import { UiModalService } from '@ui/modal';
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { BehaviorSubject, of } from 'rxjs';
import { first } from 'rxjs/operators';
import { GoodsInListItemModule } from './goods-in-list-item/goods-in-list-item.module';
import { GoodsInListService } from './goods-in-list-service';
import { GoodsInListComponent } from './goods-in-list.component';

describe('GoodsInListComponent', () => {
  let spectator: Spectator<GoodsInListComponent>;
  let goodsInListServiceMock: jasmine.SpyObj<GoodsInListService>;
  let domainOmsService: jasmine.SpyObj<DomainOmsService>;

  const createComponent = createComponentFactory({
    component: GoodsInListComponent,
    imports: [GoodsInListItemModule, RouterTestingModule, UiCommonModule, UiSpinnerModule],
    componentProviders: [
      mockProvider(GoodsInListService, {
        searchResponse$: new BehaviorSubject({}),
        loading$: new BehaviorSubject<boolean>(false),
      }),
      mockProvider(UiModalService),
      mockProvider(ProductImageService),
      mockProvider(BreadcrumbService),
    ],
    providers: [mockProvider(DomainOmsService)],
  });

  beforeEach(() => {
    spectator = createComponent();
    goodsInListServiceMock = spectator.inject(GoodsInListService, true);
    domainOmsService = spectator.inject(DomainOmsService, true);
  });

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

  describe('goods-in-list-item', () => {
    it('should be rendered for each item$', () => {
      spectator.component.items$ = of(new Array(6));
      spectator.detectComponentChanges();
      expect(spectator.queryAll('goods-in-list-item')).toHaveLength(6);
    });

    it('should have class .last on the last item', () => {
      spectator.component.items$ = of(new Array(6));
      spectator.detectComponentChanges();
      expect(spectator.queryAll('goods-in-list-item')[5]).toHaveClass('last');
    });
  });

  describe('goods-in-list-item-loading', () => {
    it('should be rendered if loading$ is true', () => {
      goodsInListServiceMock.loading$.next(true);
      spectator.detectComponentChanges();
      expect(spectator.query('goods-in-list-item-loading')).toBeVisible();
    });

    it('should not be rendered if loading$ is false', () => {
      goodsInListServiceMock.loading$.next(false);
      spectator.detectComponentChanges();
      expect(spectator.query('goods-in-list-item-loading')).not.toBeVisible();
    });
  });

  describe('button .cta-edit-ssc', () => {
    it('should be visible when editSsc is false', () => {
      spectator.component.editSsc = false;
      spectator.detectComponentChanges();
      expect(spectator.query('button.cta-edit-ssc')).toBeVisible();
    });

    it('should not be visible when editSsc is true', () => {
      spectator.component.editSsc = true;
      spectator.detectComponentChanges();
      expect(spectator.query('button.cta-edit-ssc')).not.toBeVisible();
    });

    it('should set editSsc to true on click', () => {
      spectator.component.editSsc = false;
      spectator.detectComponentChanges();

      spectator.click('button.cta-edit-ssc');
      expect(spectator.component.editSsc).toBe(true);
    });
  });

  describe('button .cta-save-ssc', () => {
    it('should be visible when editSsc is true', () => {
      spectator.component.editSsc = true;
      spectator.detectComponentChanges();
      expect(spectator.query('button.cta-save-ssc')).toBeVisible();
    });

    it('should not be visible when editSsc is false', () => {
      spectator.component.editSsc = false;
      spectator.detectComponentChanges();
      expect(spectator.query('button.cta-save-ssc')).not.toBeVisible();
    });

    it('should call saveSsc() on click', () => {
      spectator.component.editSsc = true;
      spectator.detectComponentChanges();

      spectator.component.saveSsc = jasmine.createSpy();
      spectator.click('button.cta-save-ssc');
      expect(spectator.component.saveSsc).toHaveBeenCalled();
    });

    it('should be disabled when editSscDisabled is true', () => {
      spectator.component.editSsc = true;
      spectator.component.editSscDisabled$ = of(true);
      spectator.detectComponentChanges();
      expect(spectator.query('button.cta-save-ssc')).toHaveAttribute('disabled');
    });

    it('should be enabled when editSscDisabled is false', () => {
      spectator.component.editSsc = true;
      spectator.component.editSscDisabled$ = of(false);
      spectator.detectComponentChanges();
      expect(spectator.query('button.cta-save-ssc')).not.toHaveAttribute('disabled');
    });
  });

  describe('button .cta-cancel-ssc', () => {
    it('should be visible when editSsc is true', () => {
      spectator.component.editSsc = true;
      spectator.detectComponentChanges();
      expect(spectator.query('button.cta-cancel-ssc')).toBeVisible();
    });

    it('should not be visible when editSsc is false', () => {
      spectator.component.editSsc = false;
      spectator.detectComponentChanges();
      expect(spectator.query('button.cta-cancel-ssc')).not.toBeVisible();
    });

    it('should call cancelSsc() on click', () => {
      spectator.component.editSsc = true;
      spectator.detectComponentChanges();

      spectator.component.cancelSsc = jasmine.createSpy();
      spectator.click('button.cta-cancel-ssc');
      expect(spectator.component.cancelSsc).toHaveBeenCalled();
    });
  });

  describe('editSscDisabled$', () => {
    it('should return true when a ViewChildren sscInvalid is true', async () => {
      spectator.component.items$ = of([
        { orderItemSubsetId: 1, ssc: '111', quantity: 1, product: {} },
        { orderItemSubsetId: 2, ssc: '222', quantity: 2, product: {} },
        { orderItemSubsetId: 3, ssc: '333', quantity: 3, product: {} },
      ]);
      await spectator.detectComponentChanges();
      spectator.component.listItems.forEach((listItem) => (listItem.sscChanged = true));
      spectator.component.listItems.first.sscInvalid = true;

      const disabled = await spectator.component.editSscDisabled$.pipe(first()).toPromise();
      expect(disabled).toBe(true);
    });

    it('should return true when all ViewChildren sscChanged is false', async () => {
      spectator.component.items$ = of([
        { orderItemSubsetId: 1, ssc: '111', quantity: 1, product: {} },
        { orderItemSubsetId: 2, ssc: '222', quantity: 2, product: {} },
        { orderItemSubsetId: 3, ssc: '333', quantity: 3, product: {} },
      ]);
      await spectator.detectComponentChanges();

      const disabled = await spectator.component.editSscDisabled$.pipe(first()).toPromise();
      expect(disabled).toBe(true);
    });

    it('should return false when all ViewChildren sscInvalid is false and any is changed', async () => {
      spectator.component.items$ = of([
        { orderItemSubsetId: 1, ssc: '111', quantity: 1, product: {} },
        { orderItemSubsetId: 2, ssc: '222', quantity: 2, product: {} },
        { orderItemSubsetId: 3, ssc: '333', quantity: 3, product: {} },
      ]);
      await spectator.detectComponentChanges();
      spectator.component.listItems.first.sscChanged = true;

      const disabled = await spectator.component.editSscDisabled$.pipe(first()).toPromise();
      expect(disabled).toBe(false);
    });
  });

  describe('ngOnInit()', () => {
    it('should call search', () => {
      expect(goodsInListServiceMock.search).toHaveBeenCalled();
    });
  });

  describe('ngOnDestroy()', () => {
    it('should call onDestroy$ next', () => {
      const spy = spyOn(spectator.component['_onDestroy$'], 'next');
      spectator.component.ngOnDestroy();
      expect(spy).toHaveBeenCalled();
    });

    it('should call onDestroy$ complete', () => {
      const spy = spyOn(spectator.component['_onDestroy$'], 'complete');
      spectator.component.ngOnDestroy();
      expect(spy).toHaveBeenCalled();
    });
  });

  describe('checkIfReload()', () => {
    it('should call GoodsInFilterService.search() if HTML Element contains the class last and itemLength < hits', async () => {
      goodsInListServiceMock.search = jasmine.createSpy();

      const ele = document.createElement('div');
      ele.classList.add('last');
      spectator.component.hits$ = of(6);
      spectator.component.items$ = of(new Array(5));
      spectator.detectComponentChanges();
      await spectator.component.checkIfReload(ele);
      expect(goodsInListServiceMock.search).toHaveBeenCalled();
    });

    it('should not call GoodsInFilterService.search() if itemLength >= hits', async () => {
      goodsInListServiceMock.search = jasmine.createSpy();

      const ele = document.createElement('div');
      ele.classList.add('last');
      spectator.component.hits$ = of(5);
      spectator.component.items$ = of(new Array(6));
      spectator.detectComponentChanges();
      await spectator.component.checkIfReload(ele);
      expect(goodsInListServiceMock.search).not.toHaveBeenCalled();
    });

    it('should not call GoodsInFilterService.search() if HTML Element does not contain the class last', async () => {
      goodsInListServiceMock.search = jasmine.createSpy();

      const ele = document.createElement('div');
      spectator.component.hits$ = of(6);
      spectator.component.items$ = of(new Array(5));
      spectator.detectComponentChanges();
      await spectator.component.checkIfReload(ele);
      expect(goodsInListServiceMock.search).not.toHaveBeenCalled();
    });
  });

  describe('saveSsc()', () => {
    it('should set editSsc to false', () => {
      spectator.component.editSsc = true;
      spectator.component.saveSsc();
      expect(spectator.component.editSsc).toBe(false);
    });

    it('should call changeStockStatusCode for all ViewChildren where sscChanged is true', async () => {
      domainOmsService.changeStockStatusCode = jasmine.createSpy().and.returnValue(of([]));
      spectator.component.items$ = of([
        { orderItemSubsetId: 1, ssc: '111', quantity: 1, product: {} },
        { orderItemSubsetId: 2, ssc: '222', quantity: 2, product: {} },
        { orderItemSubsetId: 3, ssc: '333', quantity: 3, product: {} },
      ]);
      spectator.detectComponentChanges();
      spectator.component.listItems.forEach((listItem) => (listItem.sscChanged = true));

      await spectator.component.saveSsc();
      expect(domainOmsService.changeStockStatusCode).toHaveBeenCalledWith([
        { id: 1, ssc: '111', quantity: 1 },
        { id: 2, ssc: '222', quantity: 2 },
        { id: 3, ssc: '333', quantity: 3 },
      ]);
    });

    it('should not call changeStockStatusCode when sscChanged is false', async () => {
      domainOmsService.changeStockStatusCode = jasmine.createSpy().and.returnValue(of([]));
      spectator.component.items$ = of([{ orderItemSubsetId: 1, ssc: '111', quantity: 1, product: {} }]);
      spectator.detectComponentChanges();

      await spectator.component.saveSsc();
      expect(domainOmsService.changeStockStatusCode).not.toHaveBeenCalled();
    });

    it('should call refreshList()', async () => {
      spectator.component.items$ = of([{ orderItemSubsetId: 1, ssc: '111', quantity: 1, product: {} }]);
      spectator.detectComponentChanges();
      spectator.component.refreshList = jasmine.createSpy();

      await spectator.component.saveSsc();
      expect(spectator.component.refreshList).toHaveBeenCalled();
    });
  });

  describe('cancelSsc()', () => {
    it('should set editSsc to false', () => {
      spectator.component.editSsc = true;
      spectator.component.cancelSsc();
      expect(spectator.component.editSsc).toBe(false);
    });
  });

  describe('refreshList()', () => {
    it('should call _goodsInListService.search() with parameter true', () => {
      goodsInListServiceMock.search = jasmine.createSpy();
      spectator.component.refreshList();
      expect(goodsInListServiceMock.search).toHaveBeenCalledWith(true);
    });
  });
});
