import { useCallback, useEffect, useRef, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { open } from '@tauri-apps/plugin-dialog';
import { load } from '@tauri-apps/plugin-store';

import "./App.css";

const FILE_STORAGE = 'launcher-settings.json';
const GAME_DIR_KEY = 'game-directory';
const CHECKING_PEERS_TIMEOUT_MS = 5000;
const FALLBACK_THUMBNAIL =
    'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8A8A';
const STORE_OPTIONS = {
    autoSave: true,
    defaults: {
        [GAME_DIR_KEY]: '',
    },
};

// enum with install status
enum InstallStatus {
    NotInstalled = 'NotInstalled',
    CheckingPeers = 'CheckingPeers',
    Downloading = 'Downloading',
    Unpacking = 'Unpacking',
    Installed = 'Installed',
}

type StatusLevel = 'info' | 'error';

type GameFilter = 'all' | 'available' | 'installed';

interface Game {
    id: string;
    name: string;
    description: string;
    size: number;
    thumbnail: Uint8Array | number[];
    downloaded: boolean;
    installed: boolean;
    install_status: InstallStatus;
    eti_game_version?: string;
    local_version?: string;
    status_message?: string;
    status_level?: StatusLevel;
    peer_count: number;
}

interface GameThumbnailProps {
    gameId: string;
    alt: string;
    getThumbnailUrl: (gameId: string) => Promise<string>;
}

const GameThumbnail = ({ gameId, alt, getThumbnailUrl }: GameThumbnailProps) => {
    const [thumbnailUrl, setThumbnailUrl] = useState('');

    useEffect(() => {
        let isMounted = true;

        const loadThumbnail = async () => {
            const url = await getThumbnailUrl(gameId);
            if (isMounted) {
                setThumbnailUrl(url);
            }
        };

        void loadThumbnail();

        return () => {
            isMounted = false;
        };
    }, [gameId, getThumbnailUrl]);

    if (!thumbnailUrl) {
        return null;
    }

    return <img src={thumbnailUrl} alt={alt} />;
};

const App = () => {
    const [gameItems, setGameItems] = useState<Game[]>([]);
    const [searchTerm, setSearchTerm] = useState('');
    const [gameDir, setGameDir] = useState('');
    const [currentFilter, setCurrentFilter] = useState<GameFilter>('available');
    const [totalPeerCount, setTotalPeerCount] = useState(0);
    const checkingPeersTimeouts = useRef<Record<string, ReturnType<typeof setTimeout>>>({});
    const [thumbnails, setThumbnails] = useState<Map<string, string>>(new Map());

    const getThumbnailUrl = useCallback(async (gameId: string): Promise<string> => {
        // Check cache first
        if (thumbnails.has(gameId)) {
            return thumbnails.get(gameId)!;
        }

        try {
            const thumbnailUrl = await invoke<string>('get_game_thumbnail', { gameId });
            setThumbnails(prev => new Map(prev).set(gameId, thumbnailUrl));
            return thumbnailUrl;
        } catch {
            // Return a small placeholder for missing images
            setThumbnails(prev => new Map(prev).set(gameId, FALLBACK_THUMBNAIL));
            return FALLBACK_THUMBNAIL;
        }
    }, [thumbnails]);

    const getFilteredGames = (games: Game[], filter: GameFilter): Game[] => {
        switch (filter) {
            case 'available':
                // Show union of installed games and games with peers
                return games.filter(game => game.installed || game.downloaded || game.peer_count > 0);
            case 'installed':
                return games.filter(game => game.installed || game.downloaded);
            case 'all':
            default:
                return games;
        }
    };

    const filteredAndSearchedGames = getFilteredGames(gameItems, currentFilter).filter(item =>
        item.name.toLowerCase().includes(searchTerm.toLowerCase())
    );

    const clearCheckingPeersTimeout = (gameId: string) => {
        const timeoutId = checkingPeersTimeouts.current[gameId];
        if (timeoutId !== undefined) {
            clearTimeout(timeoutId);
            delete checkingPeersTimeouts.current[gameId];
        }
    };

    const scheduleCheckingPeersFallback = (gameId: string, fallbackMessage?: string, fallbackLevel?: StatusLevel) => {
        clearCheckingPeersTimeout(gameId);
        checkingPeersTimeouts.current[gameId] = setTimeout(() => {
            setGameItems(prev => prev.map(item => {
                if (item.id !== gameId || item.install_status !== InstallStatus.CheckingPeers) {
                    return item;
                }
                return {
                    ...item,
                    install_status: item.installed ? InstallStatus.Installed : InstallStatus.NotInstalled,
                    status_message: fallbackMessage ?? 'No peers currently have this game.',
                    status_level: fallbackLevel ?? 'error',
                };
            }));
            delete checkingPeersTimeouts.current[gameId];
        }, CHECKING_PEERS_TIMEOUT_MS);
    };

    useEffect(() => {
        return () => {
            Object.values(checkingPeersTimeouts.current).forEach(clearTimeout);
            checkingPeersTimeouts.current = {};
        };
    }, []);

    const getInitialGameDir = useCallback(async () => {
        // update game directory from storage (if exists)
        // only if it's not already set
        await new Promise(resolve => setTimeout(resolve, 1000));
        const store = await load(FILE_STORAGE, STORE_OPTIONS);
        const savedGameDir = await store.get<string>(GAME_DIR_KEY);
        if (savedGameDir) {
            setGameDir(savedGameDir);
        }
    }, []);

    useEffect(() => {
        void getInitialGameDir();
    }, [getInitialGameDir]);

    useEffect(() => {
        // Listen for game-download-failed events specifically
        const setupDownloadFailedListener = async () => {
            const unlisten = await listen('game-download-failed', (event) => {
                const game_id = event.payload as string;
                console.log(`❌ game-download-failed ${game_id} event received`);
                clearCheckingPeersTimeout(game_id);
                setGameItems(prev => prev.map(item => item.id === game_id
                    ? {
                        ...item,
                        install_status: item.installed ? InstallStatus.Installed : InstallStatus.NotInstalled,
                        status_message: 'Download failed. Please try again.',
                        status_level: 'error',
                    }
                    : item));

                // Convert to string explicitly and verify it's not empty
                const pathString = String(gameDir);
                if (!pathString) {
                    console.error('gameDir is empty before invoke!');
                    return;
                }
                invoke('update_game_directory', { path: pathString })
                    .catch(error => console.error('❌ Error updating game directory:', error));
            });
            return unlisten;
        };

        const setupPeersGoneListener = async () => {
            const unlisten = await listen('game-download-peers-gone', (event) => {
                const game_id = event.payload as string;
                console.log(`❌ game-download-peers-gone ${game_id} event received`);
                clearCheckingPeersTimeout(game_id);
                setGameItems(prev => prev.map(item => item.id === game_id
                    ? {
                        ...item,
                        install_status: item.installed ? InstallStatus.Installed : InstallStatus.NotInstalled,
                        status_message: 'Failed: All Peers gone',
                        status_level: 'error',
                    }
                    : item));

                const pathString = String(gameDir);
                if (!pathString) {
                    console.error('gameDir is empty before invoke!');
                    return;
                }
                invoke('update_game_directory', { path: pathString })
                    .catch(error => console.error('❌ Error updating game directory:', error));
            });
            return unlisten;
        };

        const setupNoPeersListener = async () => {
            const unlisten = await listen('game-no-peers', (event) => {
                const game_id = event.payload as string;
                console.log(`⚠️ game-no-peers ${game_id} event received`);
                clearCheckingPeersTimeout(game_id);
                setGameItems(prev => prev.map(item => item.id === game_id
                    ? {
                        ...item,
                        install_status: item.installed ? InstallStatus.Installed : InstallStatus.NotInstalled,
                        status_message: 'No peers currently have this game.',
                        status_level: 'error',
                    }
                    : item));
            });
            return unlisten;
        };

        setupDownloadFailedListener();
        setupPeersGoneListener();
        setupNoPeersListener();

        const setupPeerCountListener = async () => {
            const unlisten = await listen('peer-count-updated', (event) => {
                const count = event.payload as number;
                console.log(`🗲 peer-count-updated ${count} event received`);
                setTotalPeerCount(count);
            });
            return unlisten;
        };

        setupPeerCountListener();
    }, [gameDir]);

    useEffect(() => {
        // Listen for game-unpack-finished events specifically
        const setupUnpackListener = async () => {
            const unlisten = await listen('game-unpack-finished', (event) => {
                const game_id = event.payload as string;
                console.log(`🗲 game-unpack-finished ${game_id} event received`);
                clearCheckingPeersTimeout(game_id);
                setGameItems(prev => prev.map(item => item.id === game_id
                    ? {
                        ...item,
                        install_status: InstallStatus.Installed,
                        status_message: undefined,
                        status_level: undefined,
                    }
                    : item));

                // Convert to string explicitly and verify it's not empty
                const pathString = String(gameDir);
                if (!pathString) {
                    console.error('gameDir is empty before invoke!');
                    return;
                }
                invoke('update_game_directory', { path: pathString })
                    .catch(error => console.error('❌ Error updating game directory:', error));
            });
            return unlisten;
        };

        setupUnpackListener();
    }, [gameDir]);

    useEffect(() => {
        if (gameDir) {
            // store game directory in persistent storage
            const updateStorage = async (game_dir: string) => {
                try {
                    const store = await load(FILE_STORAGE, STORE_OPTIONS);
                    await store.set(GAME_DIR_KEY, game_dir);
                    console.info(`📦 Storage updated with game directory: ${game_dir}`);
                } catch (error) {
                    console.error('❌ Error updating storage:', error);
                }
            };

            updateStorage(gameDir);

            console.log(`📂 Game directory changed to: ${gameDir}`);
            invoke('update_game_directory', { path: gameDir })
                .catch(error => console.error('❌ Error updating game directory:', error));
        }
    }, [gameDir]);

    useEffect(() => {
        console.log('🔵 Effect starting - setting up listener and requesting games');

        const setupEventListener = async () => {
            try {
                // Listen for games-list-updated events
                const unlisten_games = await listen('games-list-updated', (event) => {
                    console.log('🗲 Received games-list-updated event');
                    const games = event.payload as Game[];
                    console.log(`🎮 ${games.length} Games received`);
                    setGameItems(prev => {
                        const previousById = new Map(prev.map(item => [item.id, item]));
                        return games.map(game => {
                            const previous = previousById.get(game.id);
                            const installStatus = previous?.install_status
                                ?? (game.installed ? InstallStatus.Installed : InstallStatus.NotInstalled);
                            return {
                                ...game,
                                install_status: installStatus,
                                status_message: previous?.status_message,
                                status_level: previous?.status_level,
                                peer_count: game.peer_count ?? 0, // Ensure peer_count is always set
                            };
                        });
                    });
                    void getInitialGameDir();
                });

                // Listen for game-download-begin events
                const unlisten_game_download_begin = await listen('game-download-begin', (event) => {
                    const game_id = event.payload as string;
                    console.log(`🗲 game-download-begin ${game_id} event received`);
                    clearCheckingPeersTimeout(game_id);
                    setGameItems(prev => prev.map(item => item.id === game_id
                        ? {
                            ...item,
                            install_status: InstallStatus.Downloading,
                            status_message: undefined,
                            status_level: undefined,
                        }
                        : item));
                });

                // Listen for game-download-finished events
                const unlisten_game_download_finished = await listen('game-download-finished', (event) => {
                    const game_id = event.payload as string;
                    console.log(`🗲 game-download-finished ${game_id} event received`);
                    clearCheckingPeersTimeout(game_id);
                    setGameItems(prev => prev.map(item => item.id === game_id
                        ? {
                            ...item,
                            install_status: InstallStatus.Unpacking,
                            status_message: undefined,
                            status_level: undefined,
                        }
                        : item));
                });

                // Initial request for games
                console.log('📤 Requesting initial games list');
                await invoke('request_games');

                // Cleanup function
                return () => {
                    console.log('🧹 Cleaning up - removing listener');
                    unlisten_games();
                    unlisten_game_download_begin();
                    unlisten_game_download_finished();
                };
            } catch (error) {
                console.error('❌ Error in setup:', error);
            }
        };

        setupEventListener();

        // Cleanup
        return () => {
            console.log('🚫 Effect cleanup - component unmounting');
        };
    }, []); // Empty dependency array means this runs once on mount

    const runGame = async (id: string) => {
        console.log(`🎯 Running game with id=${id}`);
        try {
            const result = await invoke('run_game', { id });
            console.log(`✅ Game started, result=${result}`);
        } catch (error) {
            console.error('❌ Error running game:', error);
        }
    };

    const installGame = async (id: string) => {
        console.log(`🎯 Installing game with id=${id}`);
        try {
            const success = await invoke('install_game', { id });
            if (success) {
                console.log(`✅ Game install for id=${id} started...`);
                let fallbackMessage: string | undefined;
                let fallbackLevel: StatusLevel | undefined;
                // update install status in gameItems for this game
                setGameItems(prev => prev.map(item => {
                    if (item.id === id) {
                        fallbackMessage = item.status_message;
                        fallbackLevel = item.status_level;
                        return {
                            ...item,
                            install_status: InstallStatus.CheckingPeers,
                        };
                    }
                    return item;
                }));
                scheduleCheckingPeersFallback(id, fallbackMessage, fallbackLevel);
            } else {
                // game is already being installed
                console.warn(`🚧 Game with id=${id} is already being installed`);
            }
        } catch (error) {
            console.error('❌ Error installing game:', error);
        }
    };

    const updateGame = async (id: string) => {
        console.log(`🎯 Updating game with id=${id}`);
        try {
            const success = await invoke('update_game', { id });
            if (success) {
                console.log(`✅ Game update for id=${id} started...`);
                let fallbackMessage: string | undefined;
                let fallbackLevel: StatusLevel | undefined;
                // update install status in gameItems for this game
                setGameItems(prev => prev.map(item => {
                    if (item.id === id) {
                        fallbackMessage = item.status_message;
                        fallbackLevel = item.status_level;
                        return {
                            ...item,
                            install_status: InstallStatus.CheckingPeers,
                        };
                    }
                    return item;
                }));
                scheduleCheckingPeersFallback(id, fallbackMessage, fallbackLevel);
            } else {
                // game is already being installed/updated
                console.warn(`🚧 Game with id=${id} is already being updated`);
            }
        } catch (error) {
            console.error('❌ Error updating game:', error);
        }
    };

    const needsUpdate = (game: Game): boolean => {
        if (!game.installed) return false;

        // Check if peers have a version and we have a local version
        const peerVersion = game.eti_game_version;
        const localVersion = game.local_version;

        // If we don't have local version but peers have one, we need update
        if (!localVersion && peerVersion) {
            return true;
        }

        // If we have both versions, compare them numerically
        if (localVersion && peerVersion) {
            const localNum = parseInt(localVersion, 10);
            const peerNum = parseInt(peerVersion, 10);
            return peerNum > localNum;
        }

        return false;
    };

    const getInProgressLabel = (game: Game): string | undefined => {
        switch (game.install_status) {
            case InstallStatus.CheckingPeers:
                return 'Checking peers...';
            case InstallStatus.Downloading:
                return 'Downloading...';
            case InstallStatus.Unpacking:
                return 'Unpacking...';
            default:
                return undefined;
        }
    };

    const isUnavailable = (game: Game): boolean => {
        return !game.installed
            && !game.downloaded
            && game.peer_count === 0
            && game.install_status === InstallStatus.NotInstalled;
    };

    const getActionLabel = (game: Game): string => {
        const inProgress = getInProgressLabel(game);
        if (inProgress) {
            return inProgress;
        }

        if (isUnavailable(game)) {
            return 'Unavailable';
        }

        if (!game.installed) {
            return game.downloaded ? 'Install' : 'Download';
        }

        if (needsUpdate(game)) {
            return 'Update';
        }

        return 'Play';
    };

    const dialogGameDir = async () => {
        const file = await open({
            multiple: false,
            directory: true,
        });

        if (file) {
            setGameDir(file);
        }
    };

    // Rest of your component remains the same
    return (
        <main className="container">
            <div className="fixed-header">
                <div className="top-left-peer-count">
                    {totalPeerCount > 0 && (
                        <span className="peer-count">
                            👥 {totalPeerCount}
                        </span>
                    )}
                </div>
                <h1 className="align-center">SoftLAN Launcher</h1>
                <div className="main-header">
                    {gameDir ? (
                    <div>
                        <div className="filter-container">
                            <button
                                className={`filter-button ${currentFilter === 'all' ? 'active' : ''}`}
                                onClick={() => setCurrentFilter('all')}
                                title="Show all games, regardless of installation status or peer availability"
                            >
                                All Games
                            </button>
                            <button
                                className={`filter-button ${currentFilter === 'available' ? 'active' : ''}`}
                                onClick={() => setCurrentFilter('available')}
                                title="Show games that are either installed or have peers available for download"
                            >
                                Available
                            </button>
                            <button
                                className={`filter-button ${currentFilter === 'installed' ? 'active' : ''}`}
                                onClick={() => setCurrentFilter('installed')}
                                title="Show games that are installed or already downloaded to your system"
                            >
                                Installed
                            </button>
                        </div>
                        <div className="search-settings-wrapper">
                            <div></div>
                            <div className="search-container">
                                <input
                                    type="text"
                                    placeholder="Search games..."
                                    value={searchTerm}
                                    onChange={(e) => setSearchTerm(e.target.value)}
                                    className="search-input"
                                />
                            </div>
                            <div className="settings-container">
                                <button onClick={dialogGameDir} className="settings-button">Set Game Directory</button>
                                <span className="settings-text">{gameDir}</span>
                            </div>
                        </div>
                    </div>
                    ) : (
                        <div className="no-directory-container">
                            <div className="no-directory-message">
                                Please set a game directory to start scanning for games...
                            </div>
                            <div className="no-directory-button">
                                <button onClick={dialogGameDir} className="settings-button">Set Game Directory</button>
                            </div>
                        </div>
                    )}
                </div>
            </div>
            <div className="grid-container">
                {gameDir && filteredAndSearchedGames.length === 0 && gameItems.length === 0 ? (
                    <div className="no-games-message">
                        Scanning for games in your directory...
                    </div>
                ) : gameDir && filteredAndSearchedGames.length === 0 && gameItems.length > 0 ? (
                    <div className="no-games-message">
                        No games found matching your search and filters.
                    </div>
                ) : null}
                {filteredAndSearchedGames.map((item) => (
                    <div key={item.id} className="item">
                        <GameThumbnail
                            gameId={item.id}
                            alt={`${item.name} thumbnail`}
                            getThumbnailUrl={getThumbnailUrl}
                        />
                        <div className="item-name">{item.name}</div>
                        <div className="description">
                            <span className="desc-text">{item.description.slice(0, 10)}</span>
                            <span className="size-text">{(item.size / 1024 / 1024 / 1024).toFixed(1)} GB</span>
                        </div>
                        <div
                            className={`play-button${isUnavailable(item) ? ' unavailable' : ''}`}
                            onClick={() => {
                                if (isUnavailable(item)) {
                                    return;
                                }

                                if (!item.installed) {
                                    installGame(item.id);
                                } else if (needsUpdate(item)) {
                                    updateGame(item.id);
                                } else {
                                    runGame(item.id);
                                }
                            }}>
                            {getActionLabel(item)}
                        </div>
                        <div className={`item-info${item.status_level ? ` ${item.status_level}` : ''}`}>
                            <div className="status-left">
                                {item.status_message && item.peer_count === 0 && !item.installed && !item.downloaded ? item.status_message : ''}
                            </div>
                            <div className="status-right">
                                {item.peer_count > 0 && (
                                    <span className="peer-count">
                                        👥 {item.peer_count}
                                    </span>
                                )}
                            </div>
                        </div>
                    </div>
                ))}
            </div>
        </main>
    );
};

export default App;
