import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { ProductCheckoutComponent } from './product-checkout.component';
import { DeliveryOption } from 'apps/sales/src/app/core/models/delivery-option.model';
import { ProductAvailabilityService, BranchService } from '@sales/core-services';
import { of } from 'rxjs';
import { CheckoutType } from 'apps/sales/src/app/core/models/checkout-type.enum';
import { AvailabilityDTO } from '@swagger/availability';
import { NO_ERRORS_SCHEMA, ChangeDetectorRef } from '@angular/core';
import { BookPricePipe } from 'apps/sales/src/app/pipes/book-price.pipe';
import { ModalService } from '@libs/ui';
import { NgxsModule } from '@ngxs/store';
import { RouterTestingModule } from '@angular/router/testing';
import { DatePipe } from '@angular/common';
import { BranchDTO } from '@swagger/checkout';
import { ProductAvailability } from 'apps/sales/src/app/core/models/product-availability.model';

const mockAvailabilityResponse: {
  branchId: number;
  type: CheckoutType;
  av: AvailabilityDTO[];
} = {
  branchId: 1,
  type: CheckoutType.store,
  av: [
    {
      itemId: 42455413,
      requestReference: '0',
      ean: '9783957260376',
      shop: 3,
      price: { value: { value: 16.8, currency: 'EUR' }, vat: { vatType: 8 } },
      supplier: 'L',
      supplierId: 3,
      ssc: '999',
      sscText: 'Lieferbar in 1-2 Tagen',
      qty: 9.0,
      isPrebooked: false,
      at: '2020-07-16T14:00:00',
      status: 1024,
      preferred: 1,
      requested: '2020-07-15T08:28:39.5751342Z',
      requestStatusCode: '1',
      requestMessage: 'OK',
    },
    {
      itemId: 42455413,
      requestReference: '0',
      ean: '9783957260376',
      shop: 3,
      price: { value: { value: 16.8, currency: 'EUR' }, vat: { vatType: 8 } },
      supplier: 'K',
      supplierId: 4,
      ssc: '999',
      sscText: 'Lieferbar in 1-2 Tagen',
      qty: 6.0,
      isPrebooked: false,
      at: '2020-07-16T14:00:00',
      status: 1024,
      preferred: 0,
      requested: '2020-07-15T08:28:39.5754585Z',
      requestStatusCode: '1',
      requestMessage: 'OK',
    },
    {
      itemId: 42455413,
      requestReference: '0',
      ean: '9783957260376',
      shop: 3,
      price: { value: { value: 16.8, currency: 'EUR' }, vat: { vatType: 8 } },
      supplier: 'LV3',
      supplierId: 13,
      ssc: '999',
      sscText: 'Lieferbar in 10-14Tagen',
      isPrebooked: false,
      at: '2020-07-29T00:00:00',
      status: 1024,
      preferred: 0,
      requested: '2020-07-15T08:28:39.5757159Z',
      requestStatusCode: '1',
      requestMessage: 'OK',
    },
  ],
};

const mockProductAvailabilities: ProductAvailability[] = [
  {
    av: {
      at: '2020-07-15T14:00:00+02:00',
      ean: '9783957260291',
      isPrebooked: false,
      itemId: 38219742,
      logisticianId: 1002,
      preferred: 1,
      price: { value: { value: 9.99, currency: 'EUR' }, vat: { vatType: 2 } },
      qty: 1,
      requestMessage: 'Artikel ist lieferbar.',
      requestReference: '0',
      requestStatusCode: '1',
      requested: '2020-07-15T15:19:50.8364336Z',
      ssc: '005',
      sscText: 'Sofort lieferbar (Download)',
      status: 1024,
      supplier: 'DIG',
      supplierId: 41,
      supplierProductNumber: '33161986',
    },
    itemId: 38219742,
    branchId: 123,
    quantity: 1,
    status: 1024,
    type: 3,
  },
  {
    av: {
      at: '2020-08-15T14:00:00+02:00',
      ean: '9783957270291',
      isPrebooked: false,
      itemId: 38229742,
      logisticianId: 1002,
      preferred: 0,
      price: { value: { value: 3.99, currency: 'EUR' }, vat: { vatType: 2 } },
      qty: 1,
      requestMessage: 'Artikel ist lieferbar.',
      requestReference: '0',
      requestStatusCode: '1',
      requested: '2020-07-15T15:19:50.8364336Z',
      ssc: '005',
      sscText: 'Sofort lieferbar (Download)',
      status: 1024,
      supplier: 'DIG',
      supplierId: 41,
      supplierProductNumber: '33161986',
    },
    itemId: 38219742,
    branchId: 456,
    quantity: 1,
    status: 1024,
    type: 3,
  },
];

fdescribe('ProductCheckoutComponent', () => {
  let component: ProductCheckoutComponent;
  let fixture: ComponentFixture<ProductCheckoutComponent>;
  let productAvailabilityService: jasmine.SpyObj<ProductAvailabilityService>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [NgxsModule.forRoot([]), RouterTestingModule],
      declarations: [ProductCheckoutComponent, BookPricePipe],
      providers: [
        {
          provide: ProductAvailabilityService,
          useValue: jasmine.createSpyObj('productAvailabilityService', ['getStoreAvailability']),
        },
        {
          provide: ModalService,
          useValue: jasmine.createSpy('modalService'),
        },
        {
          provide: BranchService,
          useValue: jasmine.createSpy('branchService'),
        },
        DatePipe,
        ChangeDetectorRef,
      ],
      schemas: [NO_ERRORS_SCHEMA],
    })
      .overrideTemplate(ProductCheckoutComponent, '<div>Test</div>')
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ProductCheckoutComponent);
    component = fixture.componentInstance;
    productAvailabilityService = TestBed.inject(ProductAvailabilityService);

    spyOn(component, 'ngOnInit').and.callFake(() => {});

    fixture.detectChanges();
  });

  describe('get preferredDownloadAvailability', () => {
    let availabilities: ProductAvailability[];

    beforeEach(() => {
      availabilities = mockProductAvailabilities;

      component.isDownload = true;
      component.availability = availabilities;
      fixture.detectChanges();
    });

    it('should return the preferred availability', () => {
      const expected = mockProductAvailabilities[0].av;

      const result = component.preferredDownloadAvailability;
      expect(result).toEqual(expected);
    });

    it('should return undefined if availability is undefined', () => {
      component.availability = undefined;

      fixture.detectChanges();
      const result = component.preferredDownloadAvailability;

      expect(result).toBeUndefined();
    });

    it('should return undefined if no matching availabilities exist', () => {
      const availabilitiesWithTakeNowCheckoutType = mockProductAvailabilities.map((av) => ({ ...av, type: 0 }));

      component.availability = availabilitiesWithTakeNowCheckoutType;
      fixture.detectChanges();

      const result = component.preferredDownloadAvailability;
      expect(result).toBeUndefined();
    });
  });

  describe('setQuantityError', () => {
    let numberOfItems: number;
    beforeEach(() => {
      spyOn(component, 'setQuantityError').and.callThrough();
      numberOfItems = 16;
    });

    it('should return Exemplar if only 1 item is available', () => {
      const limit = 1;
      const expected = `${limit} Exemplar sofort lieferbar.`;
      component.setQuantityError(numberOfItems, limit);

      expect(component.quantityError).toEqual(expected);
    });
    it('should return Exemplar if more than 1 item is available', () => {
      const limit = 10;
      const expected = `${limit} Exemplare sofort lieferbar.`;

      component.setQuantityError(numberOfItems, limit);

      expect(component.quantityError).toEqual(expected);
    });
    it('should return a error message if requested items is larger than 999', () => {
      const limit = 10;
      numberOfItems = 1000;

      const expected = `Achtung, Maximal ${limit} Exemplare bestellbar.`;
      component.setQuantityError(numberOfItems, limit);

      expect(component.quantityError).toEqual(expected);
    });
  });

  describe('validateAvailability', () => {
    beforeEach(() => {
      spyOn(component, 'validateAvailability').and.callThrough();

      component.book = {
        product: { ean: 123 },
      };

      component.selectedBranch = { id: 789 } as BranchDTO;
    });

    it('should set current availability and pick up price if a preferred supplier is present', async () => {
      component.deliveryType = DeliveryOption.PICK_UP;
      productAvailabilityService.getStoreAvailability.and.returnValue(of(mockAvailabilityResponse));

      fixture.detectChanges();

      const result = await component.validateAvailability(9);
      const expectedCurrentAvailability = mockAvailabilityResponse.av.find((av) => av && av.preferred === 1);
      const expectedPickUpPrice = expectedCurrentAvailability.price.value.value;

      expect(component.currentAvailability).toBe(expectedCurrentAvailability);
      expect(component._pikUpPrice).toBe(expectedPickUpPrice);
    });

    it('should not set current availability and pick up price if no preferred supplier is present', () => {
      component.deliveryType = DeliveryOption.PICK_UP;
      const mockAvailabilityResponseWithoutPreferred = {
        ...mockAvailabilityResponse,
        av: mockAvailabilityResponse.av.filter((av) => av && av.preferred !== 1),
      };
      productAvailabilityService.getStoreAvailability.and.returnValue(of(mockAvailabilityResponseWithoutPreferred));

      fixture.detectChanges();

      expect(component.currentAvailability).toBeFalsy();
      expect(component._pikUpPrice).toBeFalsy();
    });

    it('should provide the sum of available quantities across all suppliers', async () => {
      component.deliveryType = DeliveryOption.PICK_UP;
      productAvailabilityService.getStoreAvailability.and.returnValue(of(mockAvailabilityResponse));
      fixture.detectChanges();

      const expected = 15;
      const result = await component.validateAvailability(9);

      expect(result).toEqual(expected);
    });

    it('should return 0 if no supplier has any quantity available', async () => {
      component.deliveryType = DeliveryOption.PICK_UP;
      const responseWithOutQuantity = setMockAvailablityResponse(mockAvailabilityResponse, 'qty', 0);
      productAvailabilityService.getStoreAvailability.and.returnValue(of(responseWithOutQuantity));
      fixture.detectChanges();

      const expected = 0;
      const result = await component.validateAvailability(9);

      expect(result).toEqual(expected);
    });
    it('should return 0 if no availabliliy has an eligible (allowed) status code', async () => {
      component.deliveryType = DeliveryOption.PICK_UP;

      const responseWithFakeStatusCode = setMockAvailablityResponse(mockAvailabilityResponse, 'status', 5);

      productAvailabilityService.getStoreAvailability.and.returnValue(of(responseWithFakeStatusCode));
      fixture.detectChanges();

      const expected = 0;
      const result = await component.validateAvailability(9);

      expect(result).toEqual(expected);
    });
  });
});

function setMockAvailablityResponse(
  mr: {
    branchId: number;
    type: CheckoutType;
    av: AvailabilityDTO[];
  },
  property: keyof AvailabilityDTO,
  value: string | number
): {
  branchId: number;
  type: CheckoutType;
  av: AvailabilityDTO[];
} {
  return {
    ...mr,
    av: (mr.av.map(({ [property]: prop, ...rest }) => ({
      rest,
      property: value,
    })) as unknown) as AvailabilityDTO[],
  };
}
