import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { DictionariesService } from '@modules/components/page-filters/services/dictionaries-service';
import { getDictionariesServiceMock } from '@modules/components/page-filters/services/dictionaries-service.mock';
import { ScheduleFilterValidationService } from '@schedule/components/schedule-filter/services/schedule-filter-validation.service';
import { IScheduleRouteDirectionParams } from '@schedule/services/url/types';
import { SCHEDULE_URL_BASE } from '@schedule/services/url/url-base';
import { ScheduleUrlBuilderService } from '@schedule/services/url/url-builder.service';
import { IScheduleRouteData } from '@schedule/types';
import { getMockPipe } from '@shared/pipes/pipe.mock';
import { APP_SETTINGS } from '@shared/services';
import { ApiParserService } from '@shared/services/api/parser.service';
import { ScheduleFiltersStateService } from '@shared/services/filters/schedule-filters-state.service';
import { SearchHistoryService } from '@shared/services/history/search-history.service';
import { UserLocationService } from '@shared/services/user-location/user-location.service';
import { getUserLocationServiceMock } from '@shared/services/user-location/user-location.service.mock';
import { StationCodeValidationService } from '@shared/services/validators/station-code.service';
import { areSame } from '@utils/date';
import { BehaviorSubject } from 'rxjs';

import { ScheduleFilterComponent } from './schedule-filter.component';

describe('ScheduleFilterComponent', () => {
    let component: ScheduleFilterComponent;
    let fixture: ComponentFixture<ScheduleFilterComponent>;
    let historyService: SearchHistoryService;
    let locationService: UserLocationService;

    const navigateByUrlMock = jasmine.createSpy('navigateByUrl');
    const data: Partial<IScheduleRouteData> = {};
    const scheduleMaxDate = new Date(2022, 5, 30);
    const scheduleMinDate = new Date(2022, 5, 1);
    const defaultOutboundParams: IScheduleRouteDirectionParams = {
        departure: 'MOW',
        arrival: 'GDX',
        dateFrom: new Date(2022, 5, 20),
        dateTo: new Date(2022, 5, 25),
    };
    const defaultInboundParams: IScheduleRouteDirectionParams = {
        departure: 'GDX',
        arrival: 'MOW',
        dateFrom: new Date(2022, 5, 22),
        dateTo: new Date(2022, 5, 27),
    };

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [ScheduleFilterComponent, getMockPipe('translate')],
            providers: [
                ScheduleFiltersStateService,
                ScheduleFilterValidationService,
                StationCodeValidationService,
                ApiParserService,
                ScheduleUrlBuilderService,
                SearchHistoryService,
                {
                    provide: DictionariesService,
                    useValue: getDictionariesServiceMock(),
                },
                {
                    provide: APP_SETTINGS,
                    useValue: {
                        scheduleMaxDate,
                        scheduleMinDate,
                    },
                },
                {
                    provide: Router,
                    useValue: {
                        navigateByUrl: navigateByUrlMock,
                    },
                },
                {
                    provide: ActivatedRoute,
                    useValue: {
                        data: new BehaviorSubject(data),
                    },
                },
                {
                    provide: SCHEDULE_URL_BASE,
                    useValue: 'schedule',
                },
                {
                    provide: UserLocationService,
                    useValue: getUserLocationServiceMock('MOW'),
                },
            ],
            schemas: [NO_ERRORS_SCHEMA],
        }).compileComponents();
    });

    beforeEach(() => {
        fixture = TestBed.createComponent(ScheduleFilterComponent);
        component = fixture.componentInstance;
        historyService = TestBed.inject(SearchHistoryService);
        locationService = TestBed.inject(UserLocationService);

        navigateByUrlMock.calls.reset();
        navigateByUrlMock.and.returnValue(Promise.resolve(true));
        jasmine.clock().install();
    });

    afterEach(() => {
        jasmine.clock().uninstall();
    });

    function setComponentOutboundParams(
        component: ScheduleFilterComponent,
        directOnly = false,
    ) {
        component.departure = 'MOW';
        component.arrival = 'GDX';
        component.directOnly = directOnly;
        component.dateRange = [new Date(2022, 4, 28), new Date(2022, 4, 30)];
    }

    function setComponentInboundParams(component: ScheduleFilterComponent) {
        component.withReturn = true;
        component.returnDateRange = [
            new Date(2022, 5, 28),
            new Date(2022, 5, 30),
        ];
    }

    it('should not emit because all params are empty', async () => {
        expect(component.departure).toBe(undefined);

        await component.viewSchedule();
        expect(navigateByUrlMock).not.toHaveBeenCalled();
    });

    it('should not emit because outbound date range is invalid', async () => {
        component.departure = 'MOW';
        component.arrival = 'GDX';
        component.withReturn = true;
        component.dateRange = [new Date('invalid')];

        await component.viewSchedule();
        expect(navigateByUrlMock).not.toHaveBeenCalled();
    });

    it('should not emit because inbound date range is invalid', async () => {
        setComponentOutboundParams(component);
        component.withReturn = true;
        component.returnDateRange = [new Date('invalid')];

        await component.viewSchedule();
        expect(navigateByUrlMock).not.toHaveBeenCalled();
    });

    it('should call navigateByUrl with url for one direction route', async () => {
        setComponentOutboundParams(component);

        const expectedResult = 'schedule/route/MOW-GDX-20220528-20220530';

        await component.viewSchedule();
        expect(navigateByUrlMock).toHaveBeenCalledWith(expectedResult);
    });

    it('should call navigateByUrl with url for one direction route with only direct flights', async () => {
        setComponentOutboundParams(component, true);

        const expectedResult = 'schedule/route/MOW-GDX-20220528-20220530-C0';

        await component.viewSchedule();
        expect(navigateByUrlMock).toHaveBeenCalledWith(expectedResult);
    });

    it('should call navigateByUrl with url for two directions route with only direct flights', async () => {
        setComponentOutboundParams(component, true);
        setComponentInboundParams(component);

        const expectedResult =
            'schedule/route/MOW-GDX-20220528-20220530-C0/GDX-MOW-20220628-20220630-C0';

        await component.viewSchedule();
        expect(navigateByUrlMock).toHaveBeenCalledWith(expectedResult);
    });

    it('should add entry to history service for one direction route', async () => {
        setComponentOutboundParams(component);

        await component.viewSchedule();
        expect(historyService.items.length).toBe(1);

        const expectedUrl = 'schedule/route/MOW-GDX-20220528-20220530';
        const historyItem = historyService.items[0];

        expect(historyItem.url).toBe(expectedUrl);
    });

    it('should not add entry to history service if navigation failed', async () => {
        navigateByUrlMock.and.returnValue(Promise.resolve(false));
        setComponentOutboundParams(component);

        await component.viewSchedule();
        expect(historyService.items.length).toBe(0);
    });

    it('should emit schedule route params for two directions route', async () => {
        setComponentOutboundParams(component);
        setComponentInboundParams(component);

        await component.viewSchedule();
        expect(historyService.items.length).toBe(1);

        const expectedUrl =
            'schedule/route/MOW-GDX-20220528-20220530/GDX-MOW-20220628-20220630';
        const historyItem = historyService.items[0];

        expect(historyItem.url).toBe(expectedUrl);
    });

    it('should exchange stations', () => {
        component.departure = 'MOW';
        component.arrival = 'GDX';
        component.exchange();

        expect(component.departure).toBe('GDX');
        expect(component.arrival).toBe('MOW');
    });

    it('should reset date range error', () => {
        component.validationService.dateRangeError = 'error';
        component.resetDateRangeError();
        expect(component.validationService.dateRangeError).toBe(undefined);
    });

    it('should reset return date range error', () => {
        component.validationService.returnDateRangeError = 'error';
        component.resetReturnDateRangeError();
        expect(component.validationService.returnDateRangeError).toBe(
            undefined,
        );
    });

    it('should reset return date range', () => {
        component.returnDateRange = [new Date(2022, 4, 28)];
        component.resetReturnDateRange();
        expect(component.returnDateRange.length).toBe(0);
    });

    it('should return second item of returnDateRange as maxScheduleDate', () => {
        const firstDate = new Date(2022, 4, 28);
        const secondDate = new Date(2022, 4, 30);
        component.returnDateRange = [firstDate, secondDate];
        expect(component.maxScheduleDate).toEqual(secondDate);
    });

    it('should return settings.maxScheduleDate as maxScheduleDate', () => {
        component.returnDateRange = undefined;
        expect(component.maxScheduleDate).toEqual(scheduleMaxDate);

        component.returnDateRange = [];
        expect(component.maxScheduleDate).toEqual(scheduleMaxDate);

        component.returnDateRange = [
            new Date(2022, 5, 25),
            new Date(2022, 6, 1),
        ];
        expect(component.maxScheduleDate).toEqual(scheduleMaxDate);
    });

    it('should return first item of dateRange as minReturnScheduleDate', () => {
        const firstDate = new Date(2022, 5, 20);
        const secondDate = new Date(2022, 5, 26);
        component.dateRange = [firstDate, secondDate];
        expect(component.minReturnScheduleDate).toEqual(firstDate);
    });

    it('should return settings.minScheduleDate as minReturnScheduleDate', () => {
        component.dateRange = undefined;
        expect(component.minReturnScheduleDate).toEqual(scheduleMinDate);

        component.dateRange = [];
        expect(component.minReturnScheduleDate).toEqual(scheduleMinDate);

        component.dateRange = [new Date(2022, 4, 28), new Date(2022, 5, 10)];
        expect(component.minReturnScheduleDate).toEqual(scheduleMinDate);
    });

    it('should set state on init when only outbound params are provided', () => {
        data.urlParams = {
            route: {
                outbound: defaultOutboundParams,
            },
        };
        component.ngOnInit();

        expect(component.withReturn).toBe(false);
        expect(component.departure).toBe('MOW');
        expect(component.arrival).toBe('GDX');
        expect(
            areSame(component.dateRange[0], defaultOutboundParams.dateFrom),
        ).toBe(true);
        expect(
            areSame(component.dateRange[1], defaultOutboundParams.dateTo),
        ).toBe(true);
        expect(component.timeRange).toEqual({});
        expect(component.directOnly).toBe(false);
    });

    it('should set outbound time range on init', () => {
        data.urlParams = {
            route: {
                outbound: {
                    ...defaultOutboundParams,
                    timeFrom: '1300',
                    timeTo: '1700',
                },
            },
        };

        component.ngOnInit();
        expect(component.timeRange.timeFrom).toBe('1300');
        expect(component.timeRange.timeTo).toBe('1700');
    });

    it('should not set outbound time range  on init because only time from is provided', () => {
        data.urlParams = {
            route: {
                outbound: {
                    ...defaultOutboundParams,
                    timeFrom: '1300',
                },
            },
        };

        component.ngOnInit();
        expect(component.timeRange).toEqual({});
    });

    it('should set directOnly to true if connections is 0', () => {
        data.urlParams = {
            route: {
                outbound: {
                    ...defaultOutboundParams,
                    connections: 0,
                },
            },
        };

        component.ngOnInit();
        expect(component.directOnly).toBe(true);
    });

    it('should set inbound params on init', () => {
        data.urlParams = {
            route: {
                outbound: defaultOutboundParams,
                inbound: defaultInboundParams,
            },
        };

        component.ngOnInit();
        expect(component.withReturn).toBe(true);
        expect(
            areSame(
                component.returnDateRange[0],
                defaultInboundParams.dateFrom,
            ),
        ).toBe(true);
        expect(
            areSame(component.returnDateRange[1], defaultInboundParams.dateTo),
        ).toBe(true);
        expect(component.returnTimeRange).toBe(undefined);
    });

    it('should set inbound time range on init', () => {
        data.urlParams = {
            route: {
                outbound: defaultOutboundParams,
                inbound: {
                    ...defaultInboundParams,
                    timeFrom: '1300',
                    timeTo: '1700',
                },
            },
        };

        component.ngOnInit();
        expect(component.returnTimeRange.timeFrom).toBe('1300');
        expect(component.returnTimeRange.timeTo).toBe('1700');
    });

    it('should set default state on init if device is not mobile', () => {
        jasmine.clock().mockDate(new Date(2022, 5, 15));
        data.urlParams = undefined;

        locationService.locate();
        component.ngOnInit();

        expect(component.departure).toBe('MOW');
        expect(areSame(component.dateRange[0], new Date(2022, 5, 13))).toBe(
            true,
        );
        expect(areSame(component.dateRange[1], new Date(2022, 5, 19))).toBe(
            true,
        );
    });
});
