import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } 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 { OnlineBoardRouteFilterValidationService } from '@online-board/components/filter/components/route-filter/services/online-board-route-filter-validation.service';
import { IOnlineBoardRouteData } from '@online-board/types';
import { getMockPipe } from '@shared/pipes/pipe.mock';
import { OnlineBoardFiltersStateService } from '@shared/services/filters/online-board-filters-state.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 MobileUtils from '@utils/mobile';
import { BehaviorSubject } from 'rxjs';

import { OnlineBoardRouteFilterComponent } from './online-board-route-filter.component';

describe('OnlineBoardRouteFilterComponent', () => {
    let component: OnlineBoardRouteFilterComponent;
    let fixture: ComponentFixture<OnlineBoardRouteFilterComponent>;
    let locationService: UserLocationService;
    const data: IOnlineBoardRouteData = {
        urlParams: {},
    };
    let isMobileMock;

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [
                OnlineBoardRouteFilterComponent,
                getMockPipe('translate'),
            ],
            providers: [
                OnlineBoardRouteFilterValidationService,
                StationCodeValidationService,
                OnlineBoardFiltersStateService,
                {
                    provide: DictionariesService,
                    useValue: getDictionariesServiceMock(),
                },
                {
                    provide: UserLocationService,
                    useValue: getUserLocationServiceMock('MOW'),
                },
                {
                    provide: ActivatedRoute,
                    useValue: {
                        data: new BehaviorSubject(data),
                    },
                },
            ],
            schemas: [NO_ERRORS_SCHEMA],
        }).compileComponents();
    });

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

        isMobileMock = spyOn(MobileUtils, 'isMobile');
        data.urlParams.route = undefined;
        jasmine.clock().install();
    });

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

    it('should return appropriate departure placeholder', () => {
        expect(component.departurePlaceholder).toBe('SHARED.CITY_PLACEHOLDER');
        component.arrival = 'MOW';
        expect(component.departurePlaceholder).toBe('SHARED.ALL_DIRECTIONS');
    });

    it('should return appropriate arrival placeholder', () => {
        expect(component.arrivalPlaceholder).toBe('SHARED.CITY_PLACEHOLDER');
        component.departure = 'MOW';
        expect(component.arrivalPlaceholder).toBe('SHARED.ALL_DIRECTIONS');
    });

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

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

    it('should not emit invalid params', () => {
        component.onSearch.emit = jasmine.createSpy('onSearch.emit');
        component.arrival = 'AAA';

        component.search();
        expect(component.onSearch.emit).not.toHaveBeenCalled();
    });

    it('should emit valid params', async () => {
        const date = new Date(2022, 4, 28);
        component.onSearch.emit = jasmine.createSpy('onSearch.emit');
        component.arrival = 'MOW';
        component.departure = 'GDX';
        component.date = date;

        await component.search();
        expect(component.onSearch.emit).toHaveBeenCalledWith({
            arrival: 'MOW',
            departure: 'GDX',
            date,
            timeFrom: undefined,
            timeTo: undefined,
        });
    });

    it('should emit valid params with time range', async () => {
        const date = new Date(2022, 4, 28);
        component.onSearch.emit = jasmine.createSpy('onSearch.emit');
        component.arrival = 'MOW';
        component.departure = 'GDX';
        component.date = date;
        component.timeRange = {
            timeFrom: '1300',
            timeTo: '1700',
        };

        await component.search();
        expect(component.onSearch.emit).toHaveBeenCalledWith({
            arrival: 'MOW',
            departure: 'GDX',
            date,
            timeFrom: '1300',
            timeTo: '1700',
        });
    });

    it('should set state on init', () => {
        const date = new Date(2022, 4, 28);
        data.urlParams.route = {
            arrival: 'MOW',
            date,
        };

        component.ngOnInit();
        expect(component.arrival).toBe('MOW');
        expect(component.date).toEqual(date);
        expect(component.timeRange).toBe(undefined);
    });

    it('should set time range on init', () => {
        data.urlParams.route = {
            arrival: 'MOW',
            date: new Date(2022, 4, 28),
            timeFrom: '1300',
            timeTo: '1700',
        };

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

    it('should set time range on init only if provided both time range fields', () => {
        data.urlParams.route = {
            arrival: 'MOW',
            date: new Date(2022, 4, 28),
            timeFrom: '1300',
        };

        component.ngOnInit();
        expect(component.timeRange).toBe(undefined);
    });

    it('should not set state on init if url params were not provided', () => {
        data.urlParams = undefined;
        const setStateMock = jasmine.createSpy('setState');
        (component as any).setState = setStateMock;

        component.ngOnInit();
        expect(setStateMock).not.toHaveBeenCalled();
        data.urlParams = {};
    });

    it('should not set state on init if where if no route field in url params', () => {
        const setStateMock = jasmine.createSpy('setState');
        (component as any).setState = setStateMock;

        component.ngOnInit();
        expect(setStateMock).not.toHaveBeenCalled();
    });

    it('should set default state on init if geolocation is permitted and device is not mobile', () => {
        isMobileMock.and.returnValue(false);
        const mockedToday = new Date(2022, 5, 6);
        jasmine.clock().mockDate(mockedToday);

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

        expect(component.departure).toBe('MOW');
        expect(areSame(component.date, mockedToday)).toBe(true);
        expect(component.timeRange).toBe(undefined);
    });

    it('should set default time range on init if geolocation is permitted and device is mobile', () => {
        isMobileMock.and.returnValue(true);
        const mockedToday = new Date(2022, 5, 6, 14, 21);
        jasmine.clock().mockDate(mockedToday);

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

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

    it('should not set default state on init if geolocation is not permitted', () => {
        component.ngOnInit();

        expect(component.departure).toBe(undefined);
        expect(component.date).toBe(undefined);
    });
});
