import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { Actions } from '@ngrx/effects';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { TranslateService } from '@ngx-translate/core';
import { EpgRuntimeBridgeService, EpgService } from '@iptvnator/epg/data-access';
import { WORKSPACE_SHELL_ACTIONS } from '@iptvnator/workspace/shell/util';
import { MockProvider } from 'ng-mocks';
import { EMPTY, of } from 'rxjs';
import { DataService, RuntimeCapabilitiesService } from '@iptvnator/services';
import {
    Language,
    Settings,
    StartupBehavior,
    STORE_KEY,
    StreamFormat,
    Theme,
    VideoPlayer,
} from '@iptvnator/shared/interfaces';
import { PlaylistActions } from '@iptvnator/m3u-state';
import { AppComponent } from './app.component';
import { ElectronServiceStub } from './services/electron.service.stub';
import { SettingsService } from './services/settings.service';

jest.spyOn(global.console, 'error').mockImplementation(() => {
    // suppress console.error output during tests
});

class MockSettingsService {
    getValueFromLocalStorage = jest.fn().mockReturnValue(of(undefined));
    changeTheme = jest.fn();
}

const DEFAULT_SETTINGS: Settings = {
    player: VideoPlayer.VideoJs,
    epgUrl: [],
    streamFormat: StreamFormat.M3u8StreamFormat,
    openStreamOnDoubleClick: false,
    language: Language.ENGLISH,
    showCaptions: false,
    showDashboard: true,
    startupBehavior: StartupBehavior.FirstView,
    showExternalPlaybackBar: true,
    theme: Theme.SystemTheme,
    mpvPlayerPath: '',
    mpvPlayerArguments: '',
    mpvReuseInstance: false,
    vlcPlayerPath: '',
    vlcPlayerArguments: '',
    vlcReuseInstance: false,
    remoteControl: false,
    remoteControlPort: 8765,
    downloadFolder: '',
    recordingFolder: '',
};

describe('AppComponent', () => {
    let component: AppComponent;
    let fixture: ComponentFixture<AppComponent>;
    let epgService: EpgService;
    let router: Router;
    let settingsService: MockSettingsService;
    let snackBar: MatSnackBar;
    let store: MockStore;
    let translateService: TranslateService;
    let runtimeCapabilities: Partial<RuntimeCapabilitiesService>;
    let epgBridge: Partial<EpgRuntimeBridgeService>;

    beforeEach(waitForAsync(() => {
        runtimeCapabilities = {
            isElectron: true,
            isMacOS: false,
        };
        epgBridge = {
            checkFreshness: jest.fn().mockResolvedValue({
                freshUrls: [],
                staleUrls: [],
            }),
            supportsImport: true,
            supportsSourceFreshness: true,
        };

        TestBed.configureTestingModule({
            imports: [AppComponent],
            providers: [
                provideMockStore(),
                {
                    provide: Actions,
                    useValue: new Actions(EMPTY),
                },
                {
                    provide: DataService,
                    useClass: ElectronServiceStub,
                },
                {
                    provide: SettingsService,
                    useClass: MockSettingsService,
                },
                {
                    provide: RuntimeCapabilitiesService,
                    useValue: runtimeCapabilities as RuntimeCapabilitiesService,
                },
                MockProvider(EpgService, {
                    fetchEpg: jest.fn(),
                }),
                {
                    provide: EpgRuntimeBridgeService,
                    useValue: epgBridge,
                },
                MockProvider(Router, {
                    navigateByUrl: jest.fn(),
                }),
                MockProvider(MatSnackBar, {
                    open: jest.fn(),
                }),
                MockProvider(TranslateService, {
                    instant: jest.fn((key: string) => key),
                    setDefaultLang: jest.fn(),
                    use: jest.fn(),
                }),
                {
                    provide: WORKSPACE_SHELL_ACTIONS,
                    useValue: {
                        openAddPlaylistDialog: jest.fn(),
                        openGlobalRecent: jest.fn(),
                        openGlobalSearch: jest.fn(),
                        openAccountInfo: jest.fn(),
                    },
                },
            ],
        })
            .overrideComponent(AppComponent, {
                set: {
                    template: '',
                },
            })
            .compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(AppComponent);
        epgService = TestBed.inject(EpgService);
        router = TestBed.inject(Router);
        settingsService = TestBed.inject(
            SettingsService
        ) as unknown as MockSettingsService;
        snackBar = TestBed.inject(MatSnackBar);
        store = TestBed.inject(MockStore);
        translateService = TestBed.inject(TranslateService);
        component = fixture.componentInstance;
    });

    it('should create the component', () => {
        expect(component).toBeTruthy();
    });

    it('should init component', () => {
        const storeDispatchSpy = jest.spyOn(store, 'dispatch');
        jest.spyOn(translateService, 'setDefaultLang');
        jest.spyOn(component, 'initSettings');

        component.ngOnInit();
        expect(storeDispatchSpy).toHaveBeenCalledWith(
            PlaylistActions.loadPlaylists()
        );
        expect(translateService.setDefaultLang).toHaveBeenCalledWith(
            Language.ENGLISH
        );
        expect(component.initSettings).toHaveBeenCalledTimes(1);
    });

    it('should navigate to the provided route', () => {
        const route = '/add-playlists';
        jest.spyOn(router, 'navigateByUrl');

        component.navigateToRoute(route);

        expect(router.navigateByUrl).toHaveBeenCalledWith(route);
    });

    it('should apply system theme when no settings are stored', () => {
        jest.spyOn(settingsService, 'changeTheme');

        component.initSettings();

        expect(settingsService.getValueFromLocalStorage).toHaveBeenCalledWith(
            STORE_KEY.Settings
        );
        expect(settingsService.changeTheme).toHaveBeenCalledWith(
            Theme.SystemTheme
        );
    });

    it('should apply saved settings and fetch stale epg data only', async () => {
        const settings: Settings = {
            ...DEFAULT_SETTINGS,
            epgUrl: ['https://example.com/epg.xml'],
            language: Language.SPANISH,
            theme: Theme.DarkTheme,
        };
        epgBridge.checkFreshness = jest.fn().mockResolvedValue({
            freshUrls: [],
            staleUrls: settings.epgUrl,
        });
        settingsService.getValueFromLocalStorage.mockReturnValue(of(settings));
        jest.spyOn(settingsService, 'changeTheme');
        jest.spyOn(translateService, 'use');

        component.initSettings();
        await fixture.whenStable();

        expect(translateService.use).toHaveBeenCalledWith(Language.SPANISH);
        expect(settingsService.changeTheme).toHaveBeenCalledWith(
            Theme.DarkTheme
        );
        expect(epgBridge.checkFreshness).toHaveBeenCalledWith(
            settings.epgUrl,
            12
        );
        expect(epgService.fetchEpg).toHaveBeenCalledWith(settings.epgUrl);
        expect(snackBar.open).not.toHaveBeenCalled();
    });

    it('does not fetch EPG settings when the EPG bridge cannot import EPG', async () => {
        const settings: Settings = {
            ...DEFAULT_SETTINGS,
            epgUrl: ['https://example.com/epg.xml'],
        };
        epgBridge.supportsImport = false;
        settingsService.getValueFromLocalStorage.mockReturnValue(of(settings));

        component.initSettings();
        await fixture.whenStable();

        expect(epgBridge.checkFreshness).not.toHaveBeenCalled();
        expect(epgService.fetchEpg).not.toHaveBeenCalled();
    });
});
