/**
 * Database service for Electron renderer process
 * Communicates with the main process database via IPC
 */

import { Injectable } from '@angular/core';
import {
    PlaylistMeta,
    XtreamBackupFavoriteItem,
    XtreamBackupHiddenCategory,
    XtreamBackupRecentlyViewedItem,
    XtreamCategory,
} from '@iptvnator/shared/interfaces';

export interface XCategoryFromDb {
    id: number;
    name: string;
    playlist_id: string;
    type: 'movies' | 'live' | 'series';
    xtream_id: number;
    hidden: boolean;
}

export interface XtreamContent {
    id: number;
    category_id: number;
    title: string;
    rating: string;
    added: string;
    poster_url: string;
    backdrop_url?: string | null;
    epg_channel_id?: string | null;
    tv_archive?: number | null;
    tv_archive_duration?: number | null;
    direct_source?: string | null;
    xtream_id: number;
    type: string;
    added_at?: string;
    viewed_at?: string;
    position?: number | null;
}

export interface XtreamPlaylist {
    id: string;
    name: string;
    serverUrl: string;
    username: string;
    password: string;
    type: string;
}

type XtreamDatabasePlaylistUpdate = {
    name?: string;
    username?: string;
    password?: string;
    serverUrl?: string;
    lastUpdated?: string;
};

type XtreamContentStream =
    | {
          category_id: string | number;
          rating?: string | number;
          rating_imdb?: string;
          last_modified?: string;
          added?: string;
          stream_icon?: string;
          poster?: string;
          cover?: string;
          name?: string;
          title?: string;
          epg_channel_id?: string;
          tv_archive?: string | number;
          tv_archive_duration?: string | number;
          direct_source?: string;
          series_id?: string | number;
          stream_id?: string | number;
      }
    | Record<string, unknown>;

export interface GlobalSearchResult extends XtreamContent {
    playlist_id: string;
    playlist_name: string;
}

export interface GlobalRecentItem extends XtreamContent {
    playlist_id: string;
    playlist_name: string;
    viewed_at: string;
}

export interface GlobalFavoriteItem extends XtreamContent {
    playlist_id: string;
    playlist_name: string;
    added_at: string;
}

export interface DbOperationEvent {
    operationId?: string;
    operation: string;
    playlistId?: string;
    status: 'started' | 'progress' | 'completed' | 'cancelled' | 'error';
    phase?: string;
    current?: number;
    total?: number;
    increment?: number;
    error?: string;
}

export interface DbOperationOptions {
    operationId?: string;
    onEvent?: (event: DbOperationEvent) => void;
}

export type XtreamImportStatus =
    | 'idle'
    | 'importing'
    | 'completed'
    | 'cancelled'
    | 'failed';

export function isDbAbortError(error: unknown): boolean {
    return error instanceof Error && error.name === 'AbortError';
}

export type GlobalRecentlyAddedKind = 'all' | 'vod' | 'series';

export type GlobalRecentlyAddedPlaylistType =
    | 'xtream'
    | 'stalker'
    | 'm3u-file'
    | 'm3u-text'
    | 'm3u-url';

export interface GlobalRecentlyAddedItem extends XtreamContent {
    playlist_id: string;
    playlist_name: string;
    added_at: string;
}

@Injectable({
    providedIn: 'root',
})
export class DatabaseService {
    private buildXtreamImportStateKey(
        playlistId: string,
        type: 'live' | 'movie' | 'series'
    ): string {
        return `xtream-import-status:${playlistId}:${type}`;
    }

    createOperationId(prefix = 'db-op'): string {
        return (
            globalThis.crypto?.randomUUID?.() ??
            `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`
        );
    }

    supportsDbOperationEvents(): boolean {
        return typeof window.electron?.onDbOperationEvent === 'function';
    }

    supportsDbOperationCancellation(): boolean {
        return typeof window.electron?.dbCancelOperation === 'function';
    }

    async cancelOperation(operationId: string): Promise<boolean> {
        if (!operationId || !this.supportsDbOperationCancellation()) {
            return false;
        }

        try {
            const result = await window.electron.dbCancelOperation(operationId);
            return result.success;
        } catch (error) {
            console.error('Error cancelling DB operation:', error);
            return false;
        }
    }

    private subscribeToOperation(
        operationId: string,
        operation: string,
        onEvent?: (event: DbOperationEvent) => void,
        onProgress?: (count: number) => void
    ): (() => void) | undefined {
        if (!onEvent && !onProgress) {
            return undefined;
        }

        if (window.electron.onDbOperationEvent) {
            return window.electron.onDbOperationEvent(
                (event: DbOperationEvent) => {
                    if (
                        event.operationId !== operationId ||
                        event.operation !== operation
                    ) {
                        return;
                    }

                    onEvent?.(event);

                    if (event.status === 'progress') {
                        onProgress?.(event.increment ?? event.current ?? 0);
                    }
                }
            );
        }

        if (onProgress && operation === 'save-content') {
            window.electron.onDbSaveContentProgress(onProgress);
            return () => window.electron.removeDbSaveContentProgress();
        }

        return undefined;
    }

    private async runWithOperationEvents<TResult>(
        prefix: string,
        operation: string,
        execute: (operationId: string) => Promise<TResult>,
        options?: DbOperationOptions,
        onProgress?: (count: number) => void
    ): Promise<TResult> {
        const operationId =
            options?.operationId ?? this.createOperationId(prefix);
        const unsubscribe = this.subscribeToOperation(
            operationId,
            operation,
            options?.onEvent,
            onProgress
        );

        try {
            return await execute(operationId);
        } finally {
            unsubscribe?.();
        }
    }

    /**
     * Delete a playlist and all its related data
     */
    async deletePlaylist(
        playlistId: string,
        options?: DbOperationOptions
    ): Promise<boolean> {
        try {
            await this.runWithOperationEvents(
                'db-delete-playlist',
                'delete-playlist',
                (operationId) =>
                    window.electron.dbDeletePlaylist(playlistId, operationId),
                options
            );
            return true;
        } catch (error) {
            if (!isDbAbortError(error)) {
                console.error('Error deleting playlist:', error);
            }
            return false;
        }
    }

    /**
     * Delete all content and categories for an Xtream playlist (for refresh).
     * Keeps the playlist entry but removes all imported data.
     */
    async deleteXtreamPlaylistContent(
        playlistId: string,
        options?: DbOperationOptions
    ): Promise<{
        success: boolean;
        favorites: XtreamBackupFavoriteItem[];
        recentlyViewed: XtreamBackupRecentlyViewedItem[];
        hiddenCategories: XtreamBackupHiddenCategory[];
    }> {
        return this.runWithOperationEvents(
            'db-delete-xtream-content',
            'delete-xtream-content',
            (operationId) =>
                window.electron.dbDeleteXtreamContent(playlistId, operationId),
            options
        );
    }

    /**
     * Restore favorites and recently viewed items after Xtream refresh
     */
    async restoreXtreamUserData(
        playlistId: string,
        favorites: XtreamBackupFavoriteItem[],
        recentlyViewed: XtreamBackupRecentlyViewedItem[],
        options?: DbOperationOptions
    ): Promise<void> {
        await this.runWithOperationEvents(
            'db-restore-xtream-user-data',
            'restore-xtream-user-data',
            (operationId) =>
                window.electron.dbRestoreXtreamUserData(
                    playlistId,
                    favorites,
                    recentlyViewed,
                    operationId
                ),
            options
        );
    }

    async deleteAllPlaylists(options?: DbOperationOptions): Promise<boolean> {
        try {
            await this.runWithOperationEvents(
                'db-delete-all-playlists',
                'delete-all-playlists',
                (operationId) =>
                    window.electron.dbDeleteAllPlaylists(operationId),
                options
            );
            return true;
        } catch (error) {
            if (!isDbAbortError(error)) {
                console.error('Error deleting all playlists:', error);
            }
            return false;
        }
    }

    /**
     * Update playlist basic info
     */
    async updateXtreamPlaylist(
        playlist: Pick<XtreamPlaylist, 'id' | 'name'>
    ): Promise<boolean> {
        try {
            await window.electron.dbUpdatePlaylist(playlist.id, {
                name: playlist.name,
            });
            return true;
        } catch (error) {
            console.error('Error updating playlist:', error);
            return false;
        }
    }

    /**
     * Update playlist details including credentials
     */
    async updateXtreamPlaylistDetails(playlist: {
        id: string;
        title?: string;
        username?: string;
        password?: string;
        serverUrl?: string;
        updateDate?: number;
    }): Promise<boolean> {
        try {
            const updates: XtreamDatabasePlaylistUpdate = {};
            if (playlist.title) updates.name = playlist.title;
            if (playlist.username) updates.username = playlist.username;
            if (playlist.password) updates.password = playlist.password;
            if (playlist.serverUrl) updates.serverUrl = playlist.serverUrl;
            if (playlist.updateDate !== undefined)
                updates.lastUpdated = new Date(
                    playlist.updateDate
                ).toISOString();

            await window.electron.dbUpdatePlaylist(playlist.id, updates);
            return true;
        } catch (error) {
            console.error('Error updating playlist details:', error);
            return false;
        }
    }

    /**
     * Check if categories exist
     */
    async hasXtreamCategories(
        playlistId: string,
        type: 'live' | 'movies' | 'series'
    ): Promise<boolean> {
        return await window.electron.dbHasCategories(playlistId, type);
    }

    /**
     * Get categories for a playlist
     */
    async getXtreamCategories(
        playlistId: string,
        type: 'live' | 'movies' | 'series'
    ): Promise<XCategoryFromDb[]> {
        return await window.electron.dbGetCategories(playlistId, type);
    }

    /**
     * Save categories in bulk
     * Optionally accepts hidden category xtreamIds to restore visibility preferences
     */
    async saveXtreamCategories(
        playlistId: string,
        categories: XtreamCategory[],
        type: 'live' | 'movies' | 'series',
        hiddenCategoryXtreamIds?: number[]
    ): Promise<void> {
        await window.electron.dbSaveCategories(
            playlistId,
            categories,
            type,
            hiddenCategoryXtreamIds
        );
    }

    /**
     * Get all categories for a playlist (including hidden, for management dialog)
     */
    async getAllXtreamCategories(
        playlistId: string,
        type: 'live' | 'movies' | 'series'
    ): Promise<XCategoryFromDb[]> {
        return await window.electron.dbGetAllCategories(playlistId, type);
    }

    /**
     * Update category visibility (show/hide categories)
     */
    async updateCategoryVisibility(
        categoryIds: number[],
        hidden: boolean
    ): Promise<boolean> {
        try {
            await window.electron.dbUpdateCategoryVisibility(
                categoryIds,
                hidden
            );
            return true;
        } catch (error) {
            console.error('Error updating category visibility:', error);
            return false;
        }
    }

    /**
     * Check if content exists
     */
    async hasXtreamContent(
        playlistId: string,
        type: 'live' | 'movie' | 'series'
    ): Promise<boolean> {
        return await window.electron.dbHasContent(playlistId, type);
    }

    /**
     * Get content for a playlist
     */
    async getXtreamContent(
        playlistId: string,
        type: 'live' | 'movie' | 'series'
    ): Promise<XtreamContent[]> {
        return await window.electron.dbGetContent(playlistId, type);
    }

    /**
     * Save content in bulk
     */
    async saveXtreamContent(
        playlistId: string,
        streams: XtreamContentStream[],
        type: 'live' | 'movie' | 'series',
        onProgress?: (count: number) => void,
        options?: DbOperationOptions
    ): Promise<number> {
        try {
            const result = await this.runWithOperationEvents(
                'db-save-content',
                'save-content',
                (operationId) =>
                    window.electron.dbSaveContent(
                        playlistId,
                        streams,
                        type,
                        operationId
                    ),
                options,
                onProgress
            );
            return (result as { count: number }).count;
        } catch (error) {
            if (!isDbAbortError(error)) {
                console.error('Error saving Xtream content:', error);
            }
            throw error;
        }
    }

    async clearXtreamImportCache(
        playlistId: string,
        type: 'live' | 'movie' | 'series'
    ): Promise<boolean> {
        if (typeof window.electron?.dbClearXtreamImportCache !== 'function') {
            return false;
        }

        try {
            await window.electron.dbClearXtreamImportCache(playlistId, type);
            return true;
        } catch (error) {
            console.error('Error clearing Xtream import cache:', error);
            return false;
        }
    }

    async getAppState(key: string): Promise<string | null> {
        if (typeof window.electron?.dbGetAppState !== 'function') {
            return null;
        }

        try {
            return await window.electron.dbGetAppState(key);
        } catch (error) {
            console.error('Error getting app state:', error);
            return null;
        }
    }

    async setAppState(key: string, value: string): Promise<boolean> {
        if (typeof window.electron?.dbSetAppState !== 'function') {
            return false;
        }

        try {
            await window.electron.dbSetAppState(key, value);
            return true;
        } catch (error) {
            console.error('Error setting app state:', error);
            return false;
        }
    }

    async getXtreamImportStatus(
        playlistId: string,
        type: 'live' | 'movie' | 'series'
    ): Promise<XtreamImportStatus> {
        const value = await this.getAppState(
            this.buildXtreamImportStateKey(playlistId, type)
        );

        switch (value) {
            case 'importing':
            case 'completed':
            case 'cancelled':
            case 'failed':
            case 'idle':
                return value;
            default:
                return 'idle';
        }
    }

    async setXtreamImportStatus(
        playlistId: string,
        type: 'live' | 'movie' | 'series',
        status: XtreamImportStatus
    ): Promise<boolean> {
        return this.setAppState(
            this.buildXtreamImportStateKey(playlistId, type),
            status
        );
    }

    /**
     * Search content within a playlist
     */
    async searchXtreamContent(
        playlistId: string,
        searchTerm: string,
        types: string[],
        excludeHidden?: boolean
    ): Promise<XtreamContent[]> {
        return await window.electron.dbSearchContent(
            playlistId,
            searchTerm,
            types,
            excludeHidden
        );
    }

    /**
     * Global search across all playlists
     */
    async globalSearchContent(
        searchTerm: string,
        types: string[],
        excludeHidden?: boolean
    ): Promise<GlobalSearchResult[]> {
        return await window.electron.dbGlobalSearch(
            searchTerm,
            types,
            excludeHidden
        );
    }

    /**
     * Get recently added VOD and series items across all Xtream playlists.
     * When `playlistType` is supplied, the LIMIT window is applied only to
     * rows from playlists of that type, so Xtream items cannot be squeezed
     * out by newer M3U/Stalker content.
     */
    async getGlobalRecentlyAdded(
        kind: GlobalRecentlyAddedKind,
        limit = 200,
        playlistType?: GlobalRecentlyAddedPlaylistType
    ): Promise<GlobalRecentlyAddedItem[]> {
        try {
            const items = await window.electron.dbGetGlobalRecentlyAdded(
                kind,
                limit,
                playlistType
            );
            return items || [];
        } catch (error) {
            console.error('Error getting global recently added items:', error);
            return [];
        }
    }

    /**
     * Get recently viewed items
     */
    async getGlobalRecentlyViewed(): Promise<GlobalRecentItem[]> {
        if (typeof window.electron?.dbGetRecentlyViewed !== 'function') {
            return [];
        }

        try {
            const items = await window.electron.dbGetRecentlyViewed();
            return items || [];
        } catch (error) {
            console.error('Error getting recently viewed:', error);
            return [];
        }
    }

    /**
     * Get global favorites across all playlists
     */
    async getGlobalFavorites(): Promise<GlobalFavoriteItem[]> {
        if (typeof window.electron?.dbGetGlobalFavorites !== 'function') {
            return [];
        }

        try {
            const items = await window.electron.dbGetGlobalFavorites();
            return items || [];
        } catch (error) {
            console.error('Error getting global favorites:', error);
            return [];
        }
    }

    /**
     * Get global favorites across all playlists (all content types)
     */
    async getAllGlobalFavorites(): Promise<GlobalFavoriteItem[]> {
        if (typeof window.electron?.dbGetAllGlobalFavorites !== 'function') {
            return [];
        }

        try {
            const items = await window.electron.dbGetAllGlobalFavorites();
            return items || [];
        } catch (error) {
            console.error('Error getting all global favorites:', error);
            return [];
        }
    }

    /**
     * Clear recently viewed items
     */
    async clearGlobalRecentlyViewed(): Promise<void> {
        if (typeof window.electron?.dbClearRecentlyViewed !== 'function') {
            return;
        }

        try {
            await window.electron.dbClearRecentlyViewed();
        } catch (error) {
            console.error('Error clearing recently viewed:', error);
            throw error;
        }
    }

    /**
     * Get playlist by ID
     */
    async getPlaylistById(playlistId: string): Promise<XtreamPlaylist | null> {
        return await window.electron.dbGetPlaylist(playlistId);
    }

    /**
     * Create a new playlist
     */
    async createPlaylist(playlist: PlaylistMeta): Promise<void> {
        await window.electron.dbCreatePlaylist({
            id: playlist._id,
            name: playlist.title,
            serverUrl: playlist.serverUrl,
            username: playlist.username,
            password: playlist.password,
            type: 'xtream',
        });
    }

    /**
     * Add content to favorites.
     * @param backdropUrl optionally persisted to `content.backdrop_url` when
     * the row doesn't already have one. Lets the dashboard hero surface a
     * cinematic backdrop without a separate round-trip.
     */
    async addToFavorites(
        contentId: number,
        playlistId: string,
        backdropUrl?: string
    ): Promise<boolean> {
        if (typeof window.electron?.dbAddFavorite !== 'function') {
            return false;
        }

        try {
            await window.electron.dbAddFavorite(
                contentId,
                playlistId,
                backdropUrl
            );
            return true;
        } catch (error) {
            console.error('Error adding to favorites:', error);
            return false;
        }
    }

    /**
     * Remove content from favorites
     */
    async removeFromFavorites(
        contentId: number,
        playlistId: string
    ): Promise<boolean> {
        if (typeof window.electron?.dbRemoveFavorite !== 'function') {
            return false;
        }

        try {
            await window.electron.dbRemoveFavorite(contentId, playlistId);
            return true;
        } catch (error) {
            console.error('Error removing from favorites:', error);
            return false;
        }
    }

    /**
     * Check if content is favorited
     */
    async isFavorite(contentId: number, playlistId: string): Promise<boolean> {
        if (typeof window.electron?.dbIsFavorite !== 'function') {
            return false;
        }

        try {
            return await window.electron.dbIsFavorite(contentId, playlistId);
        } catch (error) {
            console.error('Error checking favorite:', error);
            return false;
        }
    }

    /**
     * Get all favorites for a playlist
     */
    async getFavorites(playlistId: string): Promise<XtreamContent[]> {
        if (typeof window.electron?.dbGetFavorites !== 'function') {
            return [];
        }

        try {
            return await window.electron.dbGetFavorites(playlistId);
        } catch (error) {
            console.error('Error getting favorites:', error);
            return [];
        }
    }

    /**
     * Get recently viewed items for a specific playlist
     */
    async getRecentItems(playlistId: string): Promise<XtreamContent[]> {
        if (typeof window.electron?.dbGetRecentItems !== 'function') {
            return [];
        }

        try {
            return await window.electron.dbGetRecentItems(playlistId);
        } catch (error) {
            console.error('Error getting recent items:', error);
            return [];
        }
    }

    /**
     * Add item to recently viewed. See `addToFavorites` for `backdropUrl`.
     */
    async addRecentItem(
        contentId: number,
        playlistId: string,
        backdropUrl?: string
    ): Promise<boolean> {
        if (typeof window.electron?.dbAddRecentItem !== 'function') {
            return false;
        }

        try {
            await window.electron.dbAddRecentItem(
                contentId,
                playlistId,
                backdropUrl
            );
            return true;
        } catch (error) {
            console.error('Error adding recent item:', error);
            return false;
        }
    }

    /**
     * Persist a backdrop URL onto an Xtream content row without touching
     * recently viewed ordering or timestamps.
     */
    async setContentBackdropIfMissing(
        contentId: number,
        backdropUrl?: string
    ): Promise<boolean> {
        const normalizedBackdropUrl = backdropUrl?.trim();
        if (!normalizedBackdropUrl) {
            return true;
        }

        if (!window.electron?.dbSetContentBackdropIfMissing) {
            return true;
        }

        try {
            await window.electron.dbSetContentBackdropIfMissing(
                contentId,
                normalizedBackdropUrl
            );
            return true;
        } catch (error) {
            console.error('Error backfilling content backdrop:', error);
            return false;
        }
    }

    /**
     * Clear recently viewed for a specific playlist
     */
    async clearPlaylistRecentItems(playlistId: string): Promise<boolean> {
        if (typeof window.electron?.dbClearPlaylistRecentItems !== 'function') {
            return false;
        }

        try {
            await window.electron.dbClearPlaylistRecentItems(playlistId);
            return true;
        } catch (error) {
            console.error('Error clearing playlist recent items:', error);
            return false;
        }
    }

    /**
     * Remove specific item from recently viewed
     */
    async removeRecentItem(
        contentId: number,
        playlistId: string
    ): Promise<boolean> {
        if (typeof window.electron?.dbRemoveRecentItem !== 'function') {
            return false;
        }

        try {
            await window.electron.dbRemoveRecentItem(contentId, playlistId);
            return true;
        } catch (error) {
            console.error('Error removing recent item:', error);
            return false;
        }
    }

    async removeRecentItemsBatch(
        items: { contentId: number; playlistId: string }[]
    ): Promise<boolean> {
        if (items.length === 0) {
            return true;
        }
        if (typeof window.electron?.dbRemoveRecentItemsBatch !== 'function') {
            return false;
        }

        try {
            await window.electron.dbRemoveRecentItemsBatch(items);
            return true;
        } catch (error) {
            console.error('Error removing recent items batch:', error);
            return false;
        }
    }

    /**
     * Get content by xtream ID
     */
    async getContentByXtreamId(
        xtreamId: number,
        playlistId: string,
        contentType?: 'live' | 'movie' | 'series'
    ): Promise<XtreamContent | null> {
        if (typeof window.electron?.dbGetContentByXtreamId !== 'function') {
            return null;
        }

        try {
            return await window.electron.dbGetContentByXtreamId(
                xtreamId,
                playlistId,
                contentType
            );
        } catch (error) {
            console.error('Error getting content by xtream ID:', error);
            return null;
        }
    }
}
