import { TestBed } from '@angular/core/testing';
import { OnlineBoardApiService } from '@online-board/services/api.service';
import { OnlineBoardDataSourceService } from '@online-board/services/data-source.service';
import { IOnlineBoardRoutePageUrlParams } from '@online-board/services/url';
import { CreateLegacyFlightsService } from '@shared/services/create-legacy-flights.service';
import { IParsedFlightId } from '@typings/flight/flight-id';

describe('OnlineBoardDataSourceService', () => {
    let service: OnlineBoardDataSourceService;
    const getFlightsByFlightNumberMock = jasmine.createSpy(
        'getFlightsByFlightNumber',
    );
    const getFlightsByRouteMock = jasmine.createSpy('getFlightsByRoute');
    const getFlightDetailsMock = jasmine.createSpy('getFlightDetails');
    const createFlightsMock = jasmine.createSpy('createFlights');

    const flightNumberParams: IParsedFlightId = {
        flightNumber: '0001',
        carrier: 'SU',
        date: new Date(2022, 4, 28),
    };

    const routeParams: IOnlineBoardRoutePageUrlParams = {
        arrival: 'MOW',
        date: new Date(2022, 4, 28),
    };

    const result = {
        data: {
            routes: ['routes'],
            partners: ['partners'],
        },
    };

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
                OnlineBoardDataSourceService,
                {
                    provide: CreateLegacyFlightsService,
                    useValue: {
                        createFlights: createFlightsMock,
                    },
                },
                {
                    provide: OnlineBoardApiService,
                    useValue: {
                        getFlightsByFlightNumber: getFlightsByFlightNumberMock,
                        getFlightsByRoute: getFlightsByRouteMock,
                        getFlightDetails: getFlightDetailsMock,
                    },
                },
            ],
        });

        service = TestBed.inject(OnlineBoardDataSourceService);
        getFlightsByFlightNumberMock.calls.reset();
        getFlightsByRouteMock.calls.reset();
        getFlightDetailsMock.calls.reset();
        createFlightsMock.calls.reset();
    });

    function assertDataFields() {
        expect(service.flights).toBe(result.data.routes as any);
        expect(service.partners).toBe(result.data.partners);
        expect(createFlightsMock).toHaveBeenCalledWith(result.data.routes);
        expect(service.flightsLegacy).toBe(result.data.routes as any);
    }

    it('it should set loading before calling apiService.getFlightsByFlightNumber', () => {
        getFlightsByFlightNumberMock.and.returnValue(Promise.resolve(result));
        expect(service.loading).toBe(false);
        service.loadFlightsByFlightNumber(flightNumberParams);
        expect(service.loading).toBe(true);
    });

    it('should call apiService.getFlightsByFlightNumber with provided params', () => {
        getFlightsByFlightNumberMock.and.returnValue(Promise.resolve(result));
        service.loadFlightsByFlightNumber(flightNumberParams);
        expect(getFlightsByFlightNumberMock).toHaveBeenCalledWith(
            flightNumberParams,
        );
    });

    it('should set appropriate data fields if apiService.getFlightsByFlightNumber succeeded', async () => {
        getFlightsByFlightNumberMock.and.returnValue(Promise.resolve(result));
        createFlightsMock.and.returnValue(result.data.routes);
        service.loadFlightsByFlightNumber(flightNumberParams);

        await Promise.resolve();
        assertDataFields();

        await Promise.resolve();
        expect(service.loading).toBe(false);
    });

    it('should set loading to false if apiService.getFlightsByFlightNumber failed', async () => {
        getFlightsByFlightNumberMock.and.returnValue(Promise.reject());

        service.loadFlightsByFlightNumber(flightNumberParams);
        expect(service.loading).toBe(true);
        await Promise.resolve();
        await Promise.resolve();
        expect(service.loading).toBe(false);
    });

    it('it should set loading before calling apiService.getFlightsByRoute', () => {
        getFlightsByRouteMock.and.returnValue(Promise.resolve(result));
        expect(service.loading).toBe(false);
        service.loadFlightsByRoute(routeParams);
        expect(service.loading).toBe(true);
    });

    it('should call apiService.getFlightsByRoute with provided params', () => {
        getFlightsByRouteMock.and.returnValue(Promise.resolve(result));
        service.loadFlightsByRoute(routeParams);
        expect(getFlightsByRouteMock).toHaveBeenCalledWith(routeParams);
    });

    it('should set appropriate data fields if apiService.getFlightsByRoute succeeded', async () => {
        getFlightsByRouteMock.and.returnValue(Promise.resolve(result));
        createFlightsMock.and.returnValue(result.data.routes);
        service.loadFlightsByRoute(routeParams);

        await Promise.resolve();
        assertDataFields();

        await Promise.resolve();
        await Promise.resolve();
        expect(service.loading).toBe(false);
    });

    it('should handle apiService.getFlightsByFlightNumber failure', async () => {
        getFlightsByRouteMock.and.returnValue(Promise.resolve(result));
        createFlightsMock.and.returnValue(result.data.routes);

        // we should fill flights and flightsLegacy to check if it cleared in catch
        service.loadFlightsByRoute(routeParams);
        await Promise.resolve();
        expect(service.flights).toBe(result.data.routes as any);
        expect(service.flightsLegacy).toBe(result.data.routes as any);

        getFlightsByRouteMock.and.returnValue(Promise.reject());

        service.loadFlightsByRoute(routeParams);
        expect(service.loading).toBe(true);
        await Promise.resolve();
        await Promise.resolve();
        // should clear flights and flightsLegacy in catch
        expect(service.flights).toEqual([]);
        expect(service.flightsLegacy).toEqual([]);
        await Promise.resolve();
        expect(service.loading).toBe(false);
    });

    it('it should set loading before calling apiService.getFlightDetails if not silent', () => {
        getFlightDetailsMock.and.returnValue(Promise.resolve(result));
        expect(service.loading).toBe(false);
        service.loadFlightByFlightNumber(flightNumberParams);
        expect(service.loading).toBe(true);
    });

    it('it should not set loading before calling apiService.getFlightDetails if silent', () => {
        getFlightDetailsMock.and.returnValue(Promise.resolve(result));
        expect(service.loading).toBe(false);
        service.loadFlightByFlightNumber(flightNumberParams, true);
        expect(service.loading).toBe(false);
    });

    it('should call apiService.getFlightDetails with provided params', () => {
        getFlightDetailsMock.and.returnValue(Promise.resolve(result));
        service.loadFlightByFlightNumber(flightNumberParams);
        expect(getFlightDetailsMock).toHaveBeenCalledWith(flightNumberParams);
    });

    it('should set appropriate data fields if apiService.getFlightDetails succeeded', async () => {
        getFlightDetailsMock.and.returnValue(Promise.resolve(result));
        createFlightsMock.and.returnValue(result.data.routes);

        await service.loadFlightByFlightNumber(flightNumberParams);

        expect(service.flight).toBe(result.data.routes[0] as any);
        expect(service.flightLegacy).toBe(result.data.routes[0] as any);

        await Promise.resolve();
        expect(service.loading).toBe(false);
    });

    it('should stop loading', async () => {
        getFlightsByRouteMock.and.returnValue(Promise.resolve(result));

        expect(service.loading).toBe(false);
        service.loadFlightsByRoute(routeParams);
        expect(service.loading).toBe(true);

        service.stopLoading();
        expect(service.loading).toBe(false);
    });
});
