import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { ScheduleFlightDetailsPageComponent } from '@schedule/pages/flight-details/schedule-flight-details-page.component';
import { CurrentScheduleService } from '@schedule/pages/flight-details/services/current-schedule.service';
import { DetailsScheduleDatesService } from '@schedule/pages/flight-details/services/details-schedule-dates.service';
import { ScheduleDetailsDataSourceService } from '@schedule/services/data-source/details-data-source.service';
import { getScheduleDetailsDataSourceMock } from '@schedule/services/data-source/details-data-source.service.mock';
import { ScheduleDateValidationService } from '@schedule/services/date-validation.service';
import { ScheduleNavigationService } from '@schedule/services/navigation.service';
import { ScheduleHelpersService } from '@schedule/services/schedule-helpers.service';
import { ScheduleStateService } from '@schedule/services/state.service';
import { FLIGHTS_INFO_PARAM } from '@schedule/services/url/flights-info-param';
import { SCHEDULE_URL_BASE } from '@schedule/services/url/url-base';
import { ScheduleUrlBuilderService } from '@schedule/services/url/url-builder.service';
import {
    IScheduleRequestData,
    IScheduleRouteData,
    IScheduleUrlParams,
} from '@schedule/types';
import { getMockPipe } from '@shared/pipes/pipe.mock';
import { AppSettingsService } from '@shared/services';
import { ApiParserService } from '@shared/services/api/parser.service';
import { areSame } from '@utils/date';
import { BehaviorSubject } from 'rxjs';

describe('ScheduleFlightDetailsPageComponent', () => {
    let component: ScheduleFlightDetailsPageComponent;
    let fixture: ComponentFixture<ScheduleFlightDetailsPageComponent>;
    let scheduleState: ScheduleStateService;
    let dataSource: ScheduleDetailsDataSourceService;

    const scheduleMaxDate = new Date(2022, 5, 30);
    const scheduleMinDate = new Date(2022, 5, 1);
    const loadFlightDetailsMock = jasmine.createSpy('loadFlightDetails');
    const loadFlightsByRouteMock = jasmine.createSpy('loadFlightsByRoute');
    const navigateToRoutePageMock = jasmine.createSpy('navigateToRoutePage');
    const navigateToDetailsPageMock = jasmine.createSpy(
        'navigateToDetailsPage',
    );
    const toNotFoundMock = jasmine.createSpy('toNotFound');
    const flightsInfoParam = 'param';
    const requestData: IScheduleRequestData = {
        raw: 'schedule-route-MOW-GDX-20220606-20220612-GDX-MOW-20220613-20220619',
        parsed: {
            outbound: {
                departure: 'MOW',
                arrival: 'GDX',
                dateFrom: new Date(2022, 5, 6),
                dateTo: new Date(2022, 5, 12),
            },
            inbound: {
                departure: 'GDX',
                arrival: 'MOW',
                dateFrom: new Date(2022, 5, 13),
                dateTo: new Date(2022, 5, 19),
            },
        },
    };
    const urlParams: IScheduleUrlParams = {
        details: {
            flights: [
                {
                    date: new Date(2022, 5, 22),
                    flightNumber: '0001',
                    carrier: 'SU',
                },
            ]
        },
    };
    const data: IScheduleRouteData = {};

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [
                ScheduleFlightDetailsPageComponent,
                getMockPipe('translate'),
            ],
            providers: [
                ApiParserService,
                ScheduleStateService,
                ScheduleUrlBuilderService,
                DetailsScheduleDatesService,
                ScheduleDateValidationService,
                CurrentScheduleService,
                ScheduleHelpersService,
                {
                    provide: ScheduleNavigationService,
                    useValue: {
                        toRoutePage: navigateToRoutePageMock,
                        toDetailsPage: navigateToDetailsPageMock,
                        toNotFound: toNotFoundMock,
                    },
                },
                {
                    provide: AppSettingsService,
                    useValue: {
                        getSettings: () => {
                            return Promise.resolve({
                                scheduleMaxDate,
                                scheduleMinDate,
                            });
                        },
                    },
                },
                {
                    provide: ScheduleDetailsDataSourceService,
                    useValue: {
                        ...getScheduleDetailsDataSourceMock(),
                        loadFlightDetails: loadFlightDetailsMock,
                        loadFlightsByRoute: loadFlightsByRouteMock,
                    },
                },
                {
                    provide: FLIGHTS_INFO_PARAM,
                    useValue: flightsInfoParam,
                },
                {
                    provide: SCHEDULE_URL_BASE,
                    useValue: 'schedule',
                },
                {
                    provide: ActivatedRoute,
                    useValue: {
                        data: new BehaviorSubject(data),
                    },
                },
            ],
            schemas: [NO_ERRORS_SCHEMA],
        }).compileComponents();
    });

    beforeEach(() => {
        fixture = TestBed.createComponent(ScheduleFlightDetailsPageComponent);
        component = fixture.componentInstance;
        scheduleState = TestBed.inject(ScheduleStateService);
        dataSource = TestBed.inject(ScheduleDetailsDataSourceService);
        data.requestData = requestData;
        data.urlParams = urlParams;

        navigateToRoutePageMock.calls.reset();
        navigateToDetailsPageMock.calls.reset();
        toNotFoundMock.calls.reset();
        loadFlightDetailsMock.calls.reset();
        loadFlightsByRouteMock.calls.reset();
        loadFlightDetailsMock.and.returnValue(Promise.resolve());
        loadFlightsByRouteMock.and.returnValue(Promise.resolve());
    });

    async function setUpRedirectTest(
        component: ScheduleFlightDetailsPageComponent,
        date: Date,
        scheduleState: ScheduleStateService,
        isReturn = false,
    ) {
        component.ngOnInit();
        await Promise.resolve();
        await Promise.resolve();
        await Promise.resolve();
        await Promise.resolve();
        scheduleState.isReturnSchedule = isReturn;

        await component.handleRedirectToScheduleDate(date);

        return {
            outboundParams: navigateToRoutePageMock.calls.mostRecent().args[0],
            inboundParams: navigateToRoutePageMock.calls.mostRecent().args[1],
        };
    }

    it('should not call loadFlightDetails on init if urlParams were not provided', () => {
        data.urlParams = undefined;
        component.ngOnInit();
        expect(loadFlightDetailsMock).not.toHaveBeenCalled();
    });

    it('should navigate to route page with params from flight', async () => {
        await component.handleRedirectToScheduleDate(new Date(2022, 5, 22));
        const calledWith = navigateToRoutePageMock.calls.mostRecent().args[0];

        expect(calledWith.arrival).toBe('GDX');
        expect(calledWith.departure).toBe('MOW');
        expect(areSame(calledWith.dateFrom, new Date(2022, 5, 20))).toBe(true);
        expect(areSame(calledWith.dateTo, new Date(2022, 5, 26))).toBe(true);
    });

    it('should navigate to route page with dates range set to inbound params', async () => {
        // request is schedule-route-MOW-GDX-20220606-20220612-GDX-MOW-20220613-20220619;
        // Parsed outbound params will be:
        // departure: MOW; arrival: GDX; dateFrom: June 6, 2022; dateTo: June 12, 2022.
        // Parsed inbound params will be:
        // departure: GDX; arrival: MOW; dateFrom: June 13, 2022; dateTo: June 19, 2022.

        const { outboundParams, inboundParams } = await setUpRedirectTest(
            component,
            new Date(2022, 5, 22),
            scheduleState,
            true,
        );

        expect(outboundParams.arrival).toBe('GDX');
        expect(outboundParams.departure).toBe('MOW');
        expect(areSame(outboundParams.dateFrom, new Date(2022, 5, 6))).toBe(
            true,
        );
        expect(areSame(outboundParams.dateTo, new Date(2022, 5, 12))).toBe(
            true,
        );

        expect(inboundParams.arrival).toBe('MOW');
        expect(inboundParams.departure).toBe('GDX');
        expect(areSame(inboundParams.dateFrom, new Date(2022, 5, 20))).toBe(
            true,
        );
        expect(areSame(inboundParams.dateTo, new Date(2022, 5, 26))).toBe(true);
    });

    it('should navigate to route page with dates range set to outbound params', async () => {
        // request is schedule-route-MOW-GDX-20220606-20220612-GDX-MOW-20220613-20220619;
        // Parsed outbound params will be:
        // departure: MOW; arrival: GDX; dateFrom: June 6, 2022; dateTo: June 12, 2022.
        // Parsed inbound params will be:
        // departure: GDX; arrival: MOW; dateFrom: June 13, 2022; dateTo: June 19, 2022.

        const { outboundParams, inboundParams } = await setUpRedirectTest(
            component,
            new Date(2022, 5, 22),
            scheduleState,
        );
        expect(outboundParams.arrival).toBe('GDX');
        expect(outboundParams.departure).toBe('MOW');
        expect(areSame(outboundParams.dateFrom, new Date(2022, 5, 20))).toBe(
            true,
        );
        expect(areSame(outboundParams.dateTo, new Date(2022, 5, 26))).toBe(
            true,
        );

        expect(inboundParams.arrival).toBe('MOW');
        expect(inboundParams.departure).toBe('GDX');
        expect(areSame(inboundParams.dateFrom, new Date(2022, 5, 13))).toBe(
            true,
        );
        expect(areSame(inboundParams.dateTo, new Date(2022, 5, 19))).toBe(true);
    });

    it('should set schedule to outbound schedule', async () => {
        component.ngOnInit();
        await Promise.resolve();

        expect(component.schedule).toEqual(dataSource.scheduleLegacy);
    });

    it('should set schedule to inbound schedule', async () => {
        (dataSource as any).testUtils_swapFlight.bind(dataSource)();
        component.ngOnInit();
        await Promise.resolve();

        expect(component.schedule).toEqual(dataSource.returnScheduleLegacy);
    });

    it('it should call loadFlightsByRoute with params from request', async () => {
        // request is schedule-route-MOW-GDX-20220606-20220612-GDX-MOW-20220613-20220619;
        // Parsed outbound params will be:
        // departure: MOW; arrival: GDX; dateFrom: June 6, 2022; dateTo: June 12, 2022.
        // Parsed inbound params will be:
        // departure: GDX; arrival: MOW; dateFrom: June 13, 2022; dateTo: June 19, 2022.

        component.ngOnInit();
        await Promise.resolve();
        await Promise.resolve();
        await Promise.resolve();

        const outboundParams =
            loadFlightsByRouteMock.calls.mostRecent().args[0];
        expect(outboundParams.arrival).toBe('GDX');
        expect(outboundParams.departure).toBe('MOW');
        expect(areSame(outboundParams.dateFrom, new Date(2022, 5, 6)));
        expect(areSame(outboundParams.dateTo, new Date(2022, 5, 12)));

        const inboundParams = loadFlightsByRouteMock.calls.mostRecent().args[1];
        expect(inboundParams.arrival).toBe('MOW');
        expect(inboundParams.departure).toBe('GDX');
        expect(areSame(inboundParams.dateFrom, new Date(2022, 5, 13)));
        expect(areSame(inboundParams.dateTo, new Date(2022, 5, 19)));
    });

    it('it should call loadFlightsByRoute with params from outbound request', async () => {
        // request is schedule-route-MOW-GDX-20220606-20220612;
        // Parsed outbound params will be:
        // departure: MOW; arrival: GDX; dateFrom: June 6, 2022; dateTo: June 12, 2022.
        data.requestData = {
            raw: 'schedule-route-MOW-GDX-20220606-20220612',
            parsed: {
                outbound: {
                    departure: 'MOW',
                    arrival: 'GDX',
                    dateFrom: new Date(2022, 5, 6),
                    dateTo: new Date(2022, 5, 12),
                },
            },
        };

        component.ngOnInit();
        await Promise.resolve();
        await Promise.resolve();
        await Promise.resolve();

        const outboundParams =
            loadFlightsByRouteMock.calls.mostRecent().args[0];
        expect(outboundParams.arrival).toBe('GDX');
        expect(outboundParams.departure).toBe('MOW');
        expect(areSame(outboundParams.dateFrom, new Date(2022, 5, 6)));
        expect(areSame(outboundParams.dateTo, new Date(2022, 5, 12)));

        const inboundParams = loadFlightsByRouteMock.calls.mostRecent().args[1];
        expect(inboundParams).toBe(undefined);
    });

    it('should not call loadFlightsByRoute because request was not provided', async () => {
        data.requestData = undefined;
        component.ngOnInit();
        await Promise.resolve();
        await Promise.resolve();
        await Promise.resolve();

        expect(loadFlightsByRouteMock).not.toHaveBeenCalled();
    });

    it('should call loadFlightDetails with flight params', async () => {
        component.ngOnInit();
        await Promise.resolve();

        const args = loadFlightDetailsMock.calls.mostRecent().args;
        expect(args.length).toBe(1);
        expect(args[0].length).toBe(1);
        expect(args[0][0].flightNumber).toBe('0001');
        expect(args[0][0].carrier).toBe('SU');
        expect(areSame(args[0][0].date, new Date(2022, 5, 22))).toBe(true);
    });

    it('should set schedule after loadFlightDetails call', async () => {
        component.ngOnInit();
        await Promise.resolve();

        expect(loadFlightDetailsMock).toHaveBeenCalled();
        expect(component.schedule).not.toBe(undefined);
    });

    it('should navigate to notFound if loadFlightDetails call failed', async () => {
        loadFlightDetailsMock.and.returnValue(Promise.reject());
        component.ngOnInit();
        await Promise.resolve();
        await Promise.resolve();

        expect(toNotFoundMock).toHaveBeenCalled();
    });

    it('should call navigateToDetailsPage with provided flight and request string', async () => {
        component.ngOnInit();
        await Promise.resolve();

        const flight = {} as any;
        component.handleRedirectToFlightDetails(flight);

        expect(navigateToDetailsPageMock).toHaveBeenCalledWith(
            flight,
            data.requestData.raw,
        );
    });
});
