import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { PopularRequestComponent } from '@app/features/popular-requests/components/popular-request/popular-request.component';
import { PopularRequestsApiService } from '@app/features/popular-requests/services/api.service';
import { popularRequestsMock } from '@app/features/popular-requests/services/api.service.mock';
import { getMockPipe } from '@shared/pipes/pipe.mock';
import { OnlineBoardFiltersStateService } from '@shared/services/filters/online-board-filters-state.service';
import { ScheduleFiltersStateService } from '@shared/services/filters/schedule-filters-state.service';
import { UserLocationService } from '@shared/services/user-location/user-location.service';
import { RequestMode } from '@typings/enums';
import { areSame } from '@utils/date';
import MobileUtils from '@utils/mobile';
import { BehaviorSubject } from 'rxjs';

import { PopularRequestsComponent } from './popular-requests.component';

describe('PopularRequestsComponent', () => {
    let component: PopularRequestsComponent;
    let fixture: ComponentFixture<PopularRequestsComponent>;
    let boardState: OnlineBoardFiltersStateService;
    let scheduleState: ScheduleFiltersStateService;
    let isMobileMock;

    const today = new Date(2022, 4, 28, 13, 30);

    const navigateByUrlMock = jasmine.createSpy('navigateByUrl');
    const getPopularRequestsMock = jasmine.createSpy('getPopularRequests');
    const clearRouteMock = jasmine.createSpy('clearRoute');
    const clearScheduleMock = jasmine.createSpy('clearSchedule');

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [
                PopularRequestsComponent,
                PopularRequestComponent,
                getMockPipe('translate'),
                getMockPipe('cityName'),
            ],
            providers: [
                OnlineBoardFiltersStateService,
                ScheduleFiltersStateService,
                {
                    provide: PopularRequestsApiService,
                    useValue: {
                        getPopularRequests: getPopularRequestsMock,
                    },
                },
                {
                    provide: Router,
                    useValue: {
                        navigateByUrl: navigateByUrlMock,
                    },
                },
                {
                    provide: UserLocationService,
                    useValue: {
                        location: new BehaviorSubject('location'),
                    },
                },
            ],
            schemas: [NO_ERRORS_SCHEMA],
        }).compileComponents();
    });

    beforeEach(() => {
        fixture = TestBed.createComponent(PopularRequestsComponent);
        component = fixture.componentInstance;

        boardState = TestBed.inject(OnlineBoardFiltersStateService);
        scheduleState = TestBed.inject(ScheduleFiltersStateService);

        getPopularRequestsMock
            .withArgs()
            .and.returnValue(Promise.resolve(popularRequestsMock));
        getPopularRequestsMock.calls.reset();
        navigateByUrlMock.calls.reset();
        clearRouteMock.calls.reset();
        clearScheduleMock.calls.reset();

        (boardState.clearRoute as any) = clearRouteMock;
        (scheduleState.clear as any) = clearScheduleMock;

        isMobileMock = spyOn(MobileUtils, 'isMobile');
        isMobileMock.withArgs().and.returnValue(false);

        jasmine.clock().install();
        jasmine.clock().mockDate(today);
    });

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

    it('should set popular requests on init', async () => {
        expect(component.requests).toEqual([]);

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

        expect(component.requests).toEqual(popularRequestsMock);
    });

    it('should subscribe to location service on init', () => {
        component.ngOnInit();
        expect(component.locationPermitted).toBe(true);
    });

    it('should render four popular requests', async () => {
        expect(document.querySelectorAll('popular-request').length).toBe(0);

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

        expect(document.querySelectorAll('popular-request').length).toBe(4);
    });

    it('should emit request on click on popular-request', async () => {
        function simulateClickOnFirstRequest() {
            const firstRequest = fixture.debugElement.query(
                By.directive(PopularRequestComponent),
            );

            firstRequest.componentInstance.onRequestClick(
                popularRequestsMock[0],
            );
        }

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

        simulateClickOnFirstRequest();

        expect(boardState.selectedTab).toBe('flight');
        expect(boardState.flightNumber).toBe('9027');
        expect(boardState.flightNumberDate).toEqual(today);
    });

    it('should set arrival fields', () => {
        component.handleRequestClick({
            mode: RequestMode.ARRIVAL,
            arrival: 'MOW',
            type: 'Onlineboard',
        });

        expect(clearRouteMock).toHaveBeenCalled();
        expect(boardState.arrival).toBe('MOW');
        expect(boardState.routeDate).toEqual(today);
    });

    it('should toggle selected tab', () => {
        expect(boardState.selectedTab).toBe('route');
        component.handleRequestClick({
            mode: RequestMode.FLIGHT_NUMBER,
            flightNumber: '0001',
            carrier: 'SU',
            type: 'Onlineboard',
        });

        expect(boardState.selectedTab).toBe('flight');

        component.handleRequestClick({
            mode: RequestMode.ARRIVAL,
            arrival: 'MOW',
            type: 'Onlineboard',
        });

        expect(boardState.selectedTab).toBe('route');
    });

    it('should set mobile time range if isMobile returns true and location permitted', () => {
        isMobileMock.withArgs().and.returnValue(true);

        component.ngOnInit();
        component.handleRequestClick({
            mode: RequestMode.ARRIVAL,
            arrival: 'MOW',
            type: 'Onlineboard',
        });

        expect(boardState.timeRange).toEqual({
            timeFrom: '1200',
            timeTo: '1600',
        });
    });

    it('should not set mobile time range if isMobile returns true and location is not permitted', () => {
        isMobileMock.withArgs().and.returnValue(true);

        component.handleRequestClick({
            mode: RequestMode.ARRIVAL,
            arrival: 'MOW',
            type: 'Onlineboard',
        });

        expect(boardState.timeRange).toEqual(undefined);
    });

    it('should set departure fields', () => {
        component.handleRequestClick({
            mode: RequestMode.DEPARTURE,
            departure: 'MOW',
            type: 'Onlineboard',
        });

        expect(clearRouteMock).toHaveBeenCalled();
        expect(boardState.departure).toBe('MOW');
        expect(boardState.routeDate).toEqual(today);
    });

    it('should set online board route fields', () => {
        component.handleRequestClick({
            mode: RequestMode.ROUTE,
            departure: 'MOW',
            arrival: 'MRS',
            type: 'Onlineboard',
        });

        expect(clearRouteMock).toHaveBeenCalled();
        expect(boardState.departure).toBe('MOW');
        expect(boardState.arrival).toBe('MRS');
        expect(boardState.routeDate).toEqual(today);
    });

    it('should set schedule route fields', () => {
        component.handleRequestClick({
            mode: RequestMode.ROUTE,
            departure: 'MOW',
            arrival: 'MRS',
            type: 'Schedule',
        });

        expect(clearScheduleMock).toHaveBeenCalled();
        expect(scheduleState.departure).toBe('MOW');
        expect(scheduleState.arrival).toBe('MRS');
        expect(areSame(scheduleState.dateRange[0], new Date(2022, 4, 23))).toBe(
            true,
        );
        expect(areSame(scheduleState.dateRange[1], new Date(2022, 4, 29))).toBe(
            true,
        );
        expect(navigateByUrlMock).toHaveBeenCalledWith('schedule');
    });

    it('should set schedule route fields for both directions', () => {
        component.handleRequestClick({
            mode: RequestMode.SCHEDULE_ROUTE_BOTH_DIRECTIONS,
            departure: 'MOW',
            arrival: 'MRS',
            type: 'Schedule',
        });

        expect(clearScheduleMock).toHaveBeenCalled();
        expect(scheduleState.departure).toBe('MOW');
        expect(scheduleState.arrival).toBe('MRS');
        expect(areSame(scheduleState.dateRange[0], new Date(2022, 4, 23))).toBe(
            true,
        );
        expect(areSame(scheduleState.dateRange[1], new Date(2022, 4, 29))).toBe(
            true,
        );
        expect(scheduleState.withReturn).toBe(true);
        expect(
            areSame(scheduleState.returnDateRange[0], new Date(2022, 4, 30)),
        ).toBe(true);
        expect(
            areSame(scheduleState.returnDateRange[1], new Date(2022, 5, 5)),
        ).toBe(true);
        expect(navigateByUrlMock).toHaveBeenCalledWith('schedule');
    });
});
