use tauri::{AppHandle, Manager, State};
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use rusqlite::Connection;

use crate::{db, parser};

pub struct DbState(pub Mutex<Connection>);

#[derive(Debug, Serialize, Deserialize)]
pub struct GuideListItem {
    pub gid: String,
    pub name: String,
    pub last_synced_at: Option<String>,
    pub total_quests: usize,
    pub completed_quests: usize,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SyncResult {
    pub synced: usize,
    pub errors: Vec<String>,
}

#[tauri::command]
pub fn get_profiles(state: State<DbState>) -> Result<Vec<db::Profile>, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    db::get_profiles(&conn).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn create_profile(state: State<DbState>, name: String) -> Result<db::Profile, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    db::create_profile(&conn, &name).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn delete_profile(state: State<DbState>, profile_id: String) -> Result<(), String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    db::delete_profile(&conn, &profile_id).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn get_completed_quests(state: State<DbState>, profile_id: String) -> Result<Vec<String>, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    db::get_completed_quests(&conn, &profile_id).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn toggle_quest(state: State<DbState>, profile_id: String, quest_name: String) -> Result<bool, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    db::toggle_quest(&conn, &profile_id, &quest_name).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn get_guides_list(state: State<DbState>, profile_id: String) -> Result<Vec<GuideListItem>, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    let guides = db::get_guides(&conn).map_err(|e| e.to_string())?;
    let completed = db::get_completed_quests(&conn, &profile_id).map_err(|e| e.to_string())?;
    let completed_set: std::collections::HashSet<String> = completed.into_iter().collect();

    let items = guides.into_iter().map(|g| {
        let data: parser::GuideData = serde_json::from_str(&g.data).unwrap_or_else(|_| parser::GuideData {
            name: g.name.clone(),
            gid: g.gid.clone(),
            effect: String::new(),
            recommended_level: None,
            combat_legend: vec![],
            resources: vec![],
            sections: vec![],
        });

        let all_quests = collect_quest_names(&data);
        let total = all_quests.len();
        let completed_count = all_quests.iter().filter(|q| completed_set.contains(*q)).count();

        GuideListItem {
            gid: g.gid,
            name: g.name,
            last_synced_at: g.last_synced_at,
            total_quests: total,
            completed_quests: completed_count,
        }
    }).collect();

    Ok(items)
}

#[tauri::command]
pub fn get_guide(state: State<DbState>, gid: String) -> Result<parser::GuideData, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    let guides = db::get_guides(&conn).map_err(|e| e.to_string())?;
    if let Some(g) = guides.into_iter().find(|g| g.gid == gid) {
        serde_json::from_str(&g.data).map_err(|e| e.to_string())
    } else {
        Err(format!("Guide {} not found", gid))
    }
}

#[tauri::command]
pub async fn sync_guides(state: State<'_, DbState>) -> Result<SyncResult, String> {
    let mut synced = 0;
    let mut errors = Vec::new();

    for (gid, name) in parser::TABS {
        let gid = gid.to_string();
        let name = name.to_string();

        // Run blocking HTTP+parse in a dedicated thread to avoid runtime conflict
        let result = tokio::task::spawn_blocking({
            let gid = gid.clone();
            let name = name.clone();
            move || -> Result<String, String> {
                let csv = parser::fetch_csv(&gid)?;
                let links = parser::fetch_quest_links(&gid);
                let data = parser::parse_guide_with_links(&gid, &name, &csv, &links);
                serde_json::to_string(&data).map_err(|e| e.to_string())
            }
        }).await.map_err(|e| e.to_string())?;

        match result {
            Ok(json) => {
                let conn = state.0.lock().map_err(|e| e.to_string())?;
                db::upsert_guide(&conn, &gid, &name, &json).map_err(|e| e.to_string())?;
                synced += 1;
            }
            Err(e) => {
                errors.push(format!("{}: {}", name, e));
            }
        }
    }

    Ok(SyncResult { synced, errors })
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TabInfo {
    pub gid: String,
    pub name: String,
}

#[tauri::command]
pub fn get_tabs_list() -> Vec<TabInfo> {
    parser::TABS.iter().map(|(gid, name)| TabInfo {
        gid: gid.to_string(),
        name: name.to_string(),
    }).collect()
}

#[tauri::command]
pub async fn sync_single_guide(state: State<'_, DbState>, gid: String, name: String) -> Result<(), String> {
    let json = tokio::task::spawn_blocking({
        let gid = gid.clone();
        let name = name.clone();
        move || -> Result<String, String> {
            let csv = parser::fetch_csv(&gid)?;
            let links = parser::fetch_quest_links(&gid);
            let data = parser::parse_guide_with_links(&gid, &name, &csv, &links);
            serde_json::to_string(&data).map_err(|e| e.to_string())
        }
    }).await.map_err(|e| e.to_string())??;

    let conn = state.0.lock().map_err(|e| e.to_string())?;
    db::upsert_guide(&conn, &gid, &name, &json).map_err(|e| e.to_string())?;
    Ok(())
}

// This remains for backwards compat but delegates to per-guide approach
#[tauri::command]
pub fn has_guides(state: State<DbState>) -> Result<bool, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    let guides = db::get_guides(&conn).map_err(|e| e.to_string())?;
    Ok(!guides.is_empty())
}

#[tauri::command]
pub fn get_setting(state: State<DbState>, key: String) -> Result<Option<String>, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    Ok(db::get_setting(&conn, &key))
}

#[tauri::command]
pub fn set_setting(state: State<DbState>, key: String, value: String) -> Result<(), String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    db::set_setting(&conn, &key, &value).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn set_always_on_top(app: AppHandle, value: bool) -> Result<(), String> {
    if let Some(win) = app.get_webview_window("main") {
        win.set_always_on_top(value).map_err(|e| e.to_string())
    } else {
        Err("Window not found".to_string())
    }
}

fn collect_quest_names(data: &parser::GuideData) -> Vec<String> {
    let mut names = Vec::new();
    for section in &data.sections {
        for item in &section.items {
            match item {
                parser::SectionItem::Quest(q) => names.push(q.name.clone()),
                parser::SectionItem::Group(g) => {
                    for q in &g.quests { names.push(q.name.clone()); }
                }
                _ => {}
            }
        }
    }
    names
}
