#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

mod cache;
mod reporting;
mod utils;
mod dlc_manager;
mod installer;
mod searcher;
mod unlockers;
mod smokeapi_config;
mod config;
mod epic_scanner;
mod pe_inspector;
mod screamapi_config;
mod system_info;

use crate::config::Config;
use crate::unlockers::{CreamLinux, SmokeAPI, Unlocker};
use epic_scanner::EpicGame;
use dlc_manager::DlcInfoWithState;
use installer::{Game, InstallerAction, InstallerType};
use log::{debug, error, info, warn};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use tauri::State;
use tauri::{Emitter, Manager};
use tauri_plugin_updater::Builder as UpdaterBuilder;
use tokio::time::Instant;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GameAction {
    game_id: String,
    action: String,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum EpicAction {
    InstallScream,
    UninstallScream,
    InstallKoaloader,
    UninstallKoaloader,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EpicGameAction {
    pub game: EpicGame,
    /// "install_scream" | "uninstall_scream" | "install_koaloader" | "uninstall_koaloader"
    pub action: String,
}

#[derive(Debug, Clone)]
struct DlcCache {
    #[allow(dead_code)]
    data: Vec<DlcInfoWithState>,
    #[allow(dead_code)]
    timestamp: Instant,
}

// Structure to hold the state of installed games
pub struct AppState {
    games: Mutex<HashMap<String, Game>>,
    dlc_cache: Mutex<HashMap<String, DlcCache>>,
    fetch_cancellation: Arc<AtomicBool>,
}

// Load the current configuration
#[tauri::command]
fn load_config() -> Result<Config, String> {
    config::load_config()
}

// Update configuration
#[tauri::command]
fn update_config(config_data: Config) -> Result<Config, String> {
    config::save_config(&config_data)?;
    Ok(config_data)
}

// Add a custom Steam library path (validated to contain a steamapps folder)
#[tauri::command]
fn add_custom_steam_path(path: String) -> Result<Config, String> {
    let normalized = searcher::normalize_steam_library_path(std::path::Path::new(&path))?;
    let normalized_str = normalized.to_string_lossy().to_string();

    info!("Adding custom Steam library path: {}", normalized_str);

    config::update_config(|cfg| {
        if !cfg.custom_steam_paths.contains(&normalized_str) {
            cfg.custom_steam_paths.push(normalized_str.clone());
        }
    })
}

// Remove a custom Steam library path
#[tauri::command]
fn remove_custom_steam_path(path: String) -> Result<Config, String> {
    info!("Removing custom Steam library path: {}", path);

    config::update_config(|cfg| {
        cfg.custom_steam_paths.retain(|p| p != &path);
    })
}

// Reset configuration to defaults
#[tauri::command]
fn reset_config() -> Result<Config, String> {
    config::reset_config()
}

// Open the config folder in the system's file manager
#[tauri::command]
fn open_config_folder() -> Result<(), String> {
    config::open_config_folder()
}

// Basic host info (OS/CPU/GPU) shown on the Overview page
#[tauri::command]
fn get_system_info() -> system_info::SystemInfo {
    system_info::get_system_info()
}

#[tauri::command]
fn get_all_dlcs_command(game_path: String) -> Result<Vec<DlcInfoWithState>, String> {
    info!("Getting all DLCs (enabled and disabled) for: {}", game_path);
    dlc_manager::get_all_dlcs(&game_path)
}

#[tauri::command]
async fn scan_epic_games() -> Result<Vec<EpicGame>, String> {
    info!("Scanning for Epic games via Heroic...");
    let games = epic_scanner::scan_epic_games();
    info!("Found {} Epic games", games.len());
    Ok(games)
}

#[tauri::command]
async fn scan_steam_games(
    state: State<'_, AppState>,
    app_handle: tauri::AppHandle,
) -> Result<Vec<Game>, String> {
    info!("Starting Steam games scan");
    emit_scan_progress(&app_handle, "Locating Steam libraries...", 10);

    let mut paths = searcher::get_default_steam_paths();

    // Add user-configured custom library paths, in case a library lives
    // somewhere the auto-detection doesn't know to look.
    let cfg = config::load_config()?;
    for custom_path in &cfg.custom_steam_paths {
        let p = std::path::PathBuf::from(custom_path);
        if p.exists() && !paths.contains(&p) {
            info!("Adding custom Steam library path: {}", p.display());
            paths.push(p);
        }
    }

    emit_scan_progress(&app_handle, "Finding Steam libraries...", 15);
    let libraries = searcher::find_steam_libraries(&paths);

    let mut unique_libraries = std::collections::HashSet::new();
    for lib in &libraries {
        unique_libraries.insert(lib.to_string_lossy().to_string());
    }

    info!(
        "Found {} Steam library directories:",
        unique_libraries.len()
    );
    for (i, lib) in unique_libraries.iter().enumerate() {
        info!("  Library {}: {}", i + 1, lib);
    }

    emit_scan_progress(
        &app_handle,
        &format!(
            "Found {} Steam libraries. Starting game scan...",
            unique_libraries.len()
        ),
        20,
    );

    let games_info = searcher::find_installed_games(&libraries).await;

    emit_scan_progress(
        &app_handle,
        &format!("Found {} games. Processing...", games_info.len()),
        90,
    );

    info!("Games scan complete - Found {} games", games_info.len());
    info!(
        "Native games: {}",
        games_info.iter().filter(|g| g.native).count()
    );
    info!(
        "Proton games: {}",
        games_info.iter().filter(|g| !g.native).count()
    );
    info!(
        "Games with CreamLinux: {}",
        games_info.iter().filter(|g| g.cream_installed).count()
    );
    info!(
        "Games with SmokeAPI: {}",
        games_info.iter().filter(|g| g.smoke_installed).count()
    );

    let mut result = Vec::new();

    info!("Processing games into application state...");
    for game_info in games_info {
        debug!(
            "Processing game: {}, Native: {}, CreamLinux: {}, SmokeAPI: {}",
            game_info.title, game_info.native, game_info.cream_installed, game_info.smoke_installed
        );

        let game = Game {
            id: game_info.id,
            title: game_info.title,
            path: game_info.path.to_string_lossy().to_string(),
            native: game_info.native,
            api_files: game_info.api_files,
            cream_installed: game_info.cream_installed,
            smoke_installed: game_info.smoke_installed,
            installing: false,
        };

        result.push(game.clone());
        state.games.lock().insert(game.id.clone(), game);
    }

    emit_scan_progress(
        &app_handle,
        &format!("Scan complete. Found {} games.", result.len()),
        100,
    );

    info!("Game scan completed successfully");
    Ok(result)
}

fn emit_scan_progress(app_handle: &tauri::AppHandle, message: &str, progress: u32) {
    info!("Scan progress: {}% - {}", progress, message);

    let payload = serde_json::json!({
        "message": message,
        "progress": progress
    });

    if let Err(e) = app_handle.emit("scan-progress", payload) {
        warn!("Failed to emit scan-progress event: {}", e);
    }
}

#[tauri::command]
fn get_game_info(game_id: String, state: State<AppState>) -> Result<Game, String> {
    let games = state.games.lock();
    games
        .get(&game_id)
        .cloned()
        .ok_or_else(|| format!("Game with ID {} not found", game_id))
}

#[tauri::command]
async fn process_game_action(
    game_action: GameAction,
    state: State<'_, AppState>,
    app_handle: tauri::AppHandle,
) -> Result<Game, String> {
    let game = {
        let games = state.games.lock();
        games
            .get(&game_action.game_id)
            .cloned()
            .ok_or_else(|| format!("Game with ID {} not found", game_action.game_id))?
    };

    let (installer_type, action) = match game_action.action.as_str() {
        "install_cream" => (InstallerType::Cream, InstallerAction::Install),
        "uninstall_cream" => (InstallerType::Cream, InstallerAction::Uninstall),
        "install_smoke" => (InstallerType::Smoke, InstallerAction::Install),
        "uninstall_smoke" => (InstallerType::Smoke, InstallerAction::Uninstall),
        _ => return Err(format!("Invalid action: {}", game_action.action)),
    };

    installer::process_action(
        game_action.game_id.clone(),
        installer_type,
        action,
        game.clone(),
        app_handle.clone(),
    )
    .await?;

    let updated_game = {
        let mut games_map = state.games.lock();
        let game = games_map.get_mut(&game_action.game_id).ok_or_else(|| {
            format!(
                "Game with ID {} not found after action",
                game_action.game_id
            )
        })?;

        match (installer_type, action) {
            (InstallerType::Cream, InstallerAction::Install) => {
                game.cream_installed = true;
            }
            (InstallerType::Cream, InstallerAction::Uninstall) => {
                game.cream_installed = false;
            }
            (InstallerType::Smoke, InstallerAction::Install) => {
                game.smoke_installed = true;
            }
            (InstallerType::Smoke, InstallerAction::Uninstall) => {
                game.smoke_installed = false;
            }
        }

        game.installing = false;
        game.clone()
    };

    if let Err(e) = app_handle.emit("game-updated", &updated_game) {
        warn!("Failed to emit game-updated event: {}", e);
    }

    Ok(updated_game)
}

#[tauri::command]
async fn process_epic_action(
    epic_action: EpicGameAction,
    app_handle: tauri::AppHandle,
) -> Result<EpicGame, String> {
    let mut game = epic_action.game;
    let action = epic_action.action.as_str();
 
    info!("Processing epic action '{}' for: {}", action, game.title);
 
    game.proxy_fallback_used = false;
 
    match action {
        "install_scream" => {
            installer::install_screamapi(game.clone(), app_handle.clone()).await
                .map_err(|e| format!("Failed to install ScreamAPI: {}", e))?;
            game.scream_installed = true;
        }
        "uninstall_scream" => {
            installer::uninstall_screamapi(game.clone(), app_handle.clone()).await
                .map_err(|e| format!("Failed to uninstall ScreamAPI: {}", e))?;
            game.scream_installed = false;
        }
        "install_koaloader" => {
            let fallback_used = installer::install_koaloader(game.clone(), app_handle.clone()).await
                .map_err(|e| format!("Failed to install Koaloader: {}", e))?;
            game.koaloader_installed = true;
            game.proxy_fallback_used = fallback_used;
        }
        "uninstall_koaloader" => {
            installer::uninstall_koaloader(game.clone(), app_handle.clone()).await
                .map_err(|e| format!("Failed to uninstall Koaloader: {}", e))?;
            game.koaloader_installed = false;
        }
        _ => return Err(format!("Invalid epic action: {}", action)),
    }
 
    if let Err(e) = app_handle.emit("epic-game-updated", &game) {
        warn!("Failed to emit epic-game-updated event: {}", e);
    }
 
    Ok(game)
}

#[tauri::command]
fn read_screamapi_config(
    game_path: String,
) -> Result<Option<screamapi_config::ScreamAPIConfig>, String> {
    screamapi_config::read_config(&game_path)
}
 
#[tauri::command]
fn write_screamapi_config(
    game_path: String,
    config: screamapi_config::ScreamAPIConfig,
) -> Result<(), String> {
    screamapi_config::write_config(&game_path, &config)
}
 
#[tauri::command]
fn delete_screamapi_config(game_path: String) -> Result<(), String> {
    screamapi_config::delete_config(&game_path)
}

#[tauri::command]
async fn fetch_game_dlcs(
    game_id: String,
    state: State<'_, AppState>,
) -> Result<Vec<DlcInfoWithState>, String> {
    info!("Fetching DLC list for game ID: {}", game_id);

    // Fetch DLC data from API
    match installer::fetch_dlc_details(&game_id).await {
        Ok(dlcs) => {
            info!("Successfully fetched {} DLCs for game {}", dlcs.len(), game_id);

            // Convert to DLCInfoWithState for in-memory caching
            let dlcs_with_state = dlcs
                .into_iter()
                .map(|dlc| DlcInfoWithState {
                    appid: dlc.appid,
                    name: dlc.name,
                    enabled: true,
                })
                .collect::<Vec<_>>();

            // Update in-memory cache
            let mut dlc_cache = state.dlc_cache.lock();
            dlc_cache.insert(
                game_id.clone(),
                DlcCache {
                    data: dlcs_with_state.clone(),
                    timestamp: tokio::time::Instant::now(),
                },
            );

            Ok(dlcs_with_state)
        }
        Err(e) => {
            error!("Failed to fetch DLC details: {}", e);
            Err(format!("Failed to fetch DLC details: {}", e))
        }
    }
}

#[tauri::command]
fn abort_dlc_fetch(state: State<'_, AppState>, app_handle: tauri::AppHandle) -> Result<(), String> {
    info!("Aborting DLC fetch request received");
    state.fetch_cancellation.store(true, Ordering::SeqCst);

    // Reset cancellation flag after a short delay
    std::thread::spawn(move || {
        std::thread::sleep(std::time::Duration::from_millis(500));
        let state = app_handle.state::<AppState>();
        state.fetch_cancellation.store(false, Ordering::SeqCst);
    });

    Ok(())
}

#[tauri::command]
async fn stream_game_dlcs(game_id: String, app_handle: tauri::AppHandle) -> Result<(), String> {
    info!("Streaming DLCs for game ID: {}", game_id);

    // Fetch DLC data from API
    match installer::fetch_dlc_details_with_progress(&game_id, &app_handle).await {
        Ok(dlcs) => {
            info!(
                "Successfully streamed {} DLCs for game {}",
                dlcs.len(),
                game_id
            );

            // Convert to DLCInfoWithState for in-memory caching
            let dlcs_with_state = dlcs
                .into_iter()
                .map(|dlc| DlcInfoWithState {
                    appid: dlc.appid,
                    name: dlc.name,
                    enabled: true,
                })
                .collect::<Vec<_>>();

            // Update in-memory cache
            let state = app_handle.state::<AppState>();
            let mut dlc_cache = state.dlc_cache.lock();
            dlc_cache.insert(
                game_id.clone(),
                DlcCache {
                    data: dlcs_with_state,
                    timestamp: tokio::time::Instant::now(),
                },
            );

            Ok(())
        }
        Err(e) => {
            error!("Failed to stream DLC details: {}", e);
            // Emit error event
            let error_payload = serde_json::json!({
                "error": format!("Failed to fetch DLC details: {}", e)
            });

            if let Err(emit_err) = app_handle.emit("dlc-error", error_payload) {
                warn!("Failed to emit dlc-error event: {}", emit_err);
            }

            Err(format!("Failed to fetch DLC details: {}", e))
        }
    }
}

#[tauri::command]
fn clear_caches() -> Result<(), String> {
    info!("Clearing cached unlocker binaries");
    cache::clear_unlocker_cache()
}

#[tauri::command]
fn get_enabled_dlcs_command(game_path: String) -> Result<Vec<String>, String> {
    info!("Getting enabled DLCs for: {}", game_path);
    dlc_manager::get_enabled_dlcs(&game_path)
}

#[tauri::command]
fn update_dlc_configuration_command(
    game_path: String,
    dlcs: Vec<DlcInfoWithState>,
) -> Result<(), String> {
    info!("Updating DLC configuration for: {}", game_path);
    dlc_manager::update_dlc_configuration(&game_path, dlcs)
}

#[tauri::command]
async fn install_cream_with_dlcs_command(
    game_id: String,
    selected_dlcs: Vec<DlcInfoWithState>,
    app_handle: tauri::AppHandle,
) -> Result<Game, String> {
    info!(
        "Installing CreamLinux with selected DLCs for game: {}",
        game_id
    );

    // Clone selected_dlcs for later use
    let selected_dlcs_clone = selected_dlcs.clone();

    // Install CreamLinux with the selected DLCs
    match dlc_manager::install_cream_with_dlcs(game_id.clone(), app_handle.clone(), selected_dlcs)
        .await
    {
        Ok(_) => {
            // Return updated game info
            let state = app_handle.state::<AppState>();

            // Get a mutable reference and update the game
            let game = {
                let mut games_map = state.games.lock();
                let game = games_map.get_mut(&game_id).ok_or_else(|| {
                    format!("Game with ID {} not found after installation", game_id)
                })?;

                // Update installation status
                game.cream_installed = true;
                game.installing = false;

                // Clone the game for returning later
                game.clone()
            };

            // Emit an event to update the UI
            if let Err(e) = app_handle.emit("game-updated", &game) {
                warn!("Failed to emit game-updated event: {}", e);
            }

            // Show installation complete dialog with instructions
            let instructions = installer::InstallationInstructions {
                type_: "cream_install".to_string(),
                command: "sh ./cream.sh %command%".to_string(),
                game_title: game.title.clone(),
                dlc_count: Some(selected_dlcs_clone.iter().filter(|dlc| dlc.enabled).count()),
            };

            installer::emit_progress(
                &app_handle,
                &format!("Installation Completed: {}", game.title),
                "CreamLinux has been installed successfully!",
                100.0,
                true,
                true,
                Some(instructions),
            );

            Ok(game)
        }
        Err(e) => {
            error!("Failed to install CreamLinux with selected DLCs: {}", e);
            Err(format!(
                "Failed to install CreamLinux with selected DLCs: {}",
                e
            ))
        }
    }
}

#[tauri::command]
fn read_smokeapi_config(game_path: String) -> Result<Option<smokeapi_config::SmokeAPIConfig>, String> {
    info!("Reading SmokeAPI config for: {}", game_path);
    smokeapi_config::read_config(&game_path)
}

#[tauri::command]
fn write_smokeapi_config(
    game_path: String,
    config: smokeapi_config::SmokeAPIConfig,
) -> Result<(), String> {
    info!("Writing SmokeAPI config for: {}", game_path);
    smokeapi_config::write_config(&game_path, &config)
}

#[tauri::command]
fn delete_smokeapi_config(game_path: String) -> Result<(), String> {
    info!("Deleting SmokeAPI config for: {}", game_path);
    smokeapi_config::delete_config(&game_path)
}

#[tauri::command]
async fn resolve_platform_conflict(
    game_id: String,
    conflict_type: String, // "cream-to-proton" or "smoke-to-native"
    state: State<'_, AppState>,
    app_handle: tauri::AppHandle,
) -> Result<Game, String> {
    info!(
        "Resolving platform conflict for game {}: {}",
        game_id, conflict_type
    );

    let game = {
        let games = state.games.lock();
        games
            .get(&game_id)
            .cloned()
            .ok_or_else(|| format!("Game with ID {} not found", game_id))?
    };

    let game_title = game.title.clone();

    // Emit progress
    installer::emit_progress(
        &app_handle,
        &format!("Resolving Conflict: {}", game_title),
        "Removing conflicting files...",
        50.0,
        false,
        false,
        None,
    );

    // Perform the appropriate removal based on conflict type
    match conflict_type.as_str() {
        "cream-to-proton" => {
            // Remove CreamLinux files (bypassing native check)
            info!("Removing CreamLinux files from Proton game: {}", game_title);
            
            CreamLinux::uninstall_from_game(&game.path, &game.id)
                .await
                .map_err(|e| format!("Failed to remove CreamLinux files: {}", e))?;

            // Remove version from manifest
            crate::cache::remove_creamlinux_version(&game.path)?;
        }
        "smoke-to-native" => {
            // Remove SmokeAPI files (bypassing proton check)
            info!("Removing SmokeAPI files from native game: {}", game_title);
            
            // For native games, we need to manually remove backup files since
            // the main DLL might already be gone
            // Look for and remove *_o.dll backup files
            use walkdir::WalkDir;
            let mut removed_files = false;
            
            for entry in WalkDir::new(&game.path)
                .max_depth(5)
                .into_iter()
                .filter_map(Result::ok)
            {
                let path = entry.path();
                if !path.is_file() {
                    continue;
                }
                
                let filename = path.file_name().unwrap_or_default().to_string_lossy();
                
                // Remove steam_api*_o.dll backup files
                if filename.starts_with("steam_api") && filename.ends_with("_o.dll") {
                    match std::fs::remove_file(path) {
                        Ok(_) => {
                            info!("Removed SmokeAPI backup file: {}", path.display());
                            removed_files = true;
                        }
                        Err(e) => {
                            warn!("Failed to remove backup file {}: {}", path.display(), e);
                        }
                    }
                }
            }
            
            // Also try the normal uninstall if api_files are present
            if !game.api_files.is_empty() {
                let api_files_str = game.api_files.join(",");
                if let Err(e) = SmokeAPI::uninstall_from_game(&game.path, &api_files_str).await {
                    // Don't fail if this errors - we might have already cleaned up manually above
                    warn!("SmokeAPI uninstall warning: {}", e);
                }
            }
            
            if !removed_files {
                warn!("No SmokeAPI files found to remove for: {}", game_title);
            }

            // Remove version from manifest
            crate::cache::remove_smokeapi_version(&game.path)?;
        }
        _ => return Err(format!("Invalid conflict type: {}", conflict_type)),
    }

    installer::emit_progress(
        &app_handle,
        &format!("Conflict Resolved: {}", game_title),
        "Conflicting files have been removed successfully!",
        100.0,
        true,
        false,
        None,
    );

    // Update game state
    let updated_game = {
        let mut games_map = state.games.lock();
        let game = games_map
            .get_mut(&game_id)
            .ok_or_else(|| format!("Game with ID {} not found after conflict resolution", game_id))?;

        match conflict_type.as_str() {
            "cream-to-proton" => {
                game.cream_installed = false;
            }
            "smoke-to-native" => {
                game.smoke_installed = false;
            }
            _ => {}
        }

        game.installing = false;
        game.clone()
    };

    if let Err(e) = app_handle.emit("game-updated", &updated_game) {
        warn!("Failed to emit game-updated event: {}", e);
    }

    info!("Platform conflict resolved successfully for: {}", game_title);
    Ok(updated_game)
}

#[tauri::command]
fn set_reporting_opt_in(opted_in: bool) -> Result<(), String> {
    config::update_config(|cfg| {
        cfg.reporting_opted_in = opted_in;
        cfg.reporting_has_seen_prompt = true;
    })?;
 
    if opted_in {
        // Ensure a salt exists so future hashes work immediately
        reporting::delete_salt().ok(); // clear any stale one first
        // re-create via generate_user_hash is fine; salt is lazy-created there
    } else {
        reporting::delete_salt()?;
    }
 
    info!("Reporting opt-in set to: {}", opted_in);
    Ok(())
}
 
#[tauri::command]
async fn submit_report(
    game_id: String,
    unlocker: String,
    worked: bool,
    steam_path: String,
) -> Result<(), String> {
    let user_hash = reporting::generate_user_hash(&steam_path)?;
 
    reporting::post_report(reporting::ReportPayload {
        user_hash,
        game_id: game_id.clone(),
        unlocker: unlocker.clone(),
        worked,
    })
    .await?;

    // Always save locally so the UI can reflect the vote immediately,
    // regardless of opt-in status (the local file is only used client-side).
    reporting::save_local_report(reporting::LocalReport {
        game_id,
        unlocker,
        worked,
    })?;

    Ok(())
}

#[tauri::command]
fn get_local_reports() -> Vec<reporting::LocalReport> {
    reporting::load_local_reports()
}

#[tauri::command]
async fn get_game_votes(game_id: String) -> Result<Vec<reporting::VoteResult>, String> {
    let url = format!("https://api.shibe.fun/v1/votes/{}", game_id);

    let client = reqwest::Client::new();
    let response = client
        .get(&url)
        .timeout(std::time::Duration::from_secs(5))
        .send()
        .await
        .map_err(|e| format!("Failed to fetch votes: {}", e))?;

    if !response.status().is_success() {
        // Non-critical - return empty rather than surfacing an error to the UI
        return Ok(Vec::new());
    }

    response
        .json::<Vec<reporting::VoteResult>>()
        .await
        .map_err(|e| format!("Failed to parse votes: {}", e))
}

fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
    use log::LevelFilter;
    use log4rs::append::file::FileAppender;
    use log4rs::config::{Appender, Config, Root};
    use log4rs::encode::pattern::PatternEncoder;
    use std::fs;

    let xdg_dirs = xdg::BaseDirectories::with_prefix("creamlinux")?;
    let log_path = xdg_dirs.place_cache_file("creamlinux.log")?;

    if log_path.exists() {
        if let Err(e) = fs::write(&log_path, "") {
            eprintln!("Warning: Failed to clear log file: {}", e);
        }
    }

    let file = FileAppender::builder()
        .encoder(Box::new(PatternEncoder::new(
            "[{d(%Y-%m-%d %H:%M:%S)}] {l}: {m}\n",
        )))
        .build(log_path)?;

    let config = Config::builder()
        .appender(Appender::builder().build("file", Box::new(file)))
        .build(Root::builder().appender("file").build(LevelFilter::Info))?;

    log4rs::init_config(config)?;

    info!("CreamLinux started with a clean log file");
    Ok(())
}

fn main() {
    if let Err(e) = setup_logging() {
        eprintln!("Warning: Failed to initialize logging: {}", e);
    }

    info!("Initializing CreamLinux application");

    tauri::Builder::default()
        .plugin(UpdaterBuilder::new().build())
        .plugin(tauri_plugin_process::init())
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_fs::init())
        .invoke_handler(tauri::generate_handler![
            scan_steam_games,
            get_game_info,
            process_game_action,
            fetch_game_dlcs,
            stream_game_dlcs,
            get_enabled_dlcs_command,
            update_dlc_configuration_command,
            install_cream_with_dlcs_command,
            get_all_dlcs_command,
            clear_caches,
            abort_dlc_fetch,
            read_smokeapi_config,
            write_smokeapi_config,
            delete_smokeapi_config,
            resolve_platform_conflict,
            load_config,
            update_config,
            add_custom_steam_path,
            remove_custom_steam_path,
            reset_config,
            open_config_folder,
            get_system_info,
            set_reporting_opt_in,
            submit_report,
            get_local_reports,
            get_game_votes,
            scan_epic_games,
            process_epic_action,
            read_screamapi_config,
            write_screamapi_config,
            delete_screamapi_config,
        ])
        .setup(|app| {
            info!("Tauri application setup");

            #[cfg(debug_assertions)]
            {
                if std::env::var("OPEN_DEVTOOLS").ok().as_deref() == Some("1") {
                    if let Some(window) = app.get_webview_window("main") {
                        window.open_devtools();
                    }
                }
            }

            let app_handle = app.handle().clone();
            let state = AppState {
                games: Mutex::new(HashMap::new()),
                dlc_cache: Mutex::new(HashMap::new()),
                fetch_cancellation: Arc::new(AtomicBool::new(false)),
            };
            app.manage(state);

            // Initialize cache on startup in a background task
            tauri::async_runtime::spawn(async move {
                info!("Starting cache initialization...");

                // Step 1: Initialize cache if needed (downloads unlockers)
                if let Err(e) = cache::initialize_cache().await {
                    error!("Failed to initialize cache: {}", e);
                    return;
                }

                info!("Cache initialized successfully");

                // Step 2: Check for updates
                match cache::check_and_update_cache().await {
                    Ok(result) => {
                        if result.any_updated() {
                            info!(
                                "Updates found - SmokeAPI: {:?}, CreamLinux: {:?}, ScreamAPI: {:?}, Koaloader: {:?}",
                                result.new_smokeapi_version, result.new_creamlinux_version,
                                result.new_screamapi_version, result.new_koaloader_version
                            );

                            // Step 3: Update outdated games
                            let state_for_update = app_handle.state::<AppState>();
                            let games = state_for_update.games.lock().clone();

                            match cache::update_outdated_games(&games).await {
                                Ok(stats) => {
                                    info!(
                                        "Game updates complete - {} games updated, {} failed",
                                        stats.total_updated(),
                                        stats.total_failed()
                                    );

                                    if stats.has_failures() {
                                        warn!(
                                            "Some game updates failed: SmokeAPI failed: {}, CreamLinux failed: {}",
                                            stats.smokeapi_failed, stats.creamlinux_failed
                                        );
                                    }
                                }
                                Err(e) => {
                                    error!("Failed to update games: {}", e);
                                }
                            }
                        } else {
                            info!("All unlockers are up to date");
                        }
                    }
                    Err(e) => {
                        error!("Failed to check for updates: {}", e);
                    }
                }
            });

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}