//! 后端 HTTP API 封装 + 窗口控制命令。

use serde_json::{json, Value};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::OnceLock;
use tauri::{AppHandle, Manager};

/// 协议平台标识（18H）：mac / win / linux（区别于 std::env::consts::OS 的 macos/windows）。
const PLATFORM: &str = if cfg!(target_os = "macos") {
    "mac"
} else if cfg!(target_os = "windows") {
    "win"
} else {
    "linux"
};

/// 共享 reqwest::Client（18H）：复用连接池，避免每次请求新建 Client。
fn http() -> &'static reqwest::Client {
    static CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
    CLIENT.get_or_init(reqwest::Client::new)
}

/// 全局暂停开关（11B 托盘菜单"暂停使用"）：暂停时快捷键不再触发听写。
#[derive(Default)]
pub struct Paused(AtomicBool);

pub fn is_app_paused(app: &AppHandle) -> bool {
    app.state::<Paused>().0.load(Ordering::Relaxed)
}

#[tauri::command]
pub fn is_paused(app: AppHandle) -> bool {
    is_app_paused(&app)
}

/// 切换暂停状态，返回新状态。
#[tauri::command]
pub fn toggle_pause(app: AppHandle) -> bool {
    let paused = &app.state::<Paused>().0;
    let next = !paused.load(Ordering::Relaxed);
    paused.store(next, Ordering::Relaxed);
    if next {
        crate::dictation::stop(&app, true); // 录音中暂停 → 直接取消
    }
    if let Some(tray) = app.tray_by_id("main") {
        let _ = tray.set_tooltip(Some(if next { "dudu — 已暂停" } else { "dudu — 就绪" }));
    }
    next
}

fn base(app: &AppHandle) -> (String, String) {
    let s = app.state::<crate::settings::SettingsStore>().get();
    (s.server_url, s.token)
}

async fn get_json(app: &AppHandle, path: &str, authed: bool) -> Option<Value> {
    let (url, token) = base(app);
    let mut req = http().get(format!("{url}{path}"));
    if authed {
        if token.is_empty() {
            return None;
        }
        req = req.bearer_auth(token);
    }
    let resp = req.send().await.ok()?;
    if !resp.status().is_success() {
        return None;
    }
    resp.json().await.ok()
}

#[tauri::command]
pub async fn fetch_me(app: AppHandle) -> Option<Value> {
    get_json(&app, "/v1/me", true).await
}

#[tauri::command]
pub async fn fetch_packs(app: AppHandle) -> Option<Value> {
    let v = get_json(&app, "/v1/packs", false).await?;
    v.get("packs").cloned()
}

#[tauri::command]
pub async fn login_qr_create(app: AppHandle) -> Option<Value> {
    let (url, _) = base(&app);
    http()
        .post(format!("{url}/v1/auth/qr"))
        .send()
        .await
        .ok()?
        .json()
        .await
        .ok()
}

#[tauri::command]
pub async fn login_qr_poll(app: AppHandle, state: String) -> Option<Value> {
    let v = get_json(&app, &format!("/v1/auth/qr/{state}"), false).await?;
    if v.get("status").and_then(|s| s.as_str()) == Some("confirmed") {
        if let Some(token) = v.get("token").and_then(|t| t.as_str()) {
            let store = app.state::<crate::settings::SettingsStore>();
            let mut s = store.get();
            s.token = token.to_string();
            store.set(s);
            // 通知 ws 重连携带新 token（bounded channel：用 WsHandle::send 非阻塞投递）
            let _ = app.state::<crate::ws::WsHandle>().send(crate::ws::WsCmd::Reconnect);
        }
    }
    Some(v)
}

/// 发送邮箱登录验证码。返回 true=已发送，false=网络失败，
/// Some(json) 形式的错误码（如 RATE_LIMITED 冷却中）由前端据 status 判断。
#[tauri::command]
pub async fn login_email_code(app: AppHandle, email: String) -> Option<u16> {
    let (url, _) = base(&app);
    let resp = http()
        .post(format!("{url}/v1/auth/email/code"))
        .json(&serde_json::json!({ "email": email }))
        .send()
        .await
        .ok()?;
    Some(resp.status().as_u16())
}

/// 邮箱验证码登录：成功保存 token 并重连 ws（与扫码登录同路径）。
#[tauri::command]
pub async fn login_email(app: AppHandle, email: String, code: String) -> Option<Value> {
    let (url, _) = base(&app);
    let v: Value = http()
        .post(format!("{url}/v1/auth/email"))
        .json(&serde_json::json!({ "email": email, "code": code }))
        .send()
        .await
        .ok()?
        .json()
        .await
        .ok()?;
    if let Some(token) = v.get("token").and_then(|t| t.as_str()) {
        let store = app.state::<crate::settings::SettingsStore>();
        let mut s = store.get();
        s.token = token.to_string();
        store.set(s);
        let _ = app.state::<crate::ws::WsHandle>().send(crate::ws::WsCmd::Reconnect);
    }
    Some(v)
}

#[tauri::command]
pub async fn create_order(app: AppHandle, pack_id: String) -> Option<Value> {
    let (url, token) = base(&app);
    http()
        .post(format!("{url}/v1/orders"))
        .bearer_auth(token)
        .json(&serde_json::json!({"pack_id": pack_id, "channel": "native"}))
        .send()
        .await
        .ok()?
        .json()
        .await
        .ok()
}

#[tauri::command]
pub async fn order_status(app: AppHandle, order_id: String) -> Option<Value> {
    get_json(&app, &format!("/v1/orders/{order_id}"), true).await
}

// ─── 反馈（11E）──────────────────────────────────────────────────────────────

#[derive(serde::Deserialize)]
pub struct FeedbackImage {
    pub name: String,
    /// base64（不含 data: 前缀）
    pub data: String,
}

fn image_mime(name: &str) -> &'static str {
    let lower = name.to_ascii_lowercase();
    if lower.ends_with(".png") {
        "image/png"
    } else if lower.ends_with(".webp") {
        "image/webp"
    } else {
        "image/jpeg"
    }
}

#[cfg(target_os = "macos")]
fn os_version() -> String {
    std::process::Command::new("sw_vers")
        .arg("-productVersion")
        .output()
        .ok()
        .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
        .unwrap_or_default()
}

#[cfg(not(target_os = "macos"))]
fn os_version() -> String {
    String::new()
}

/// multipart POST /v1/feedback：文字 + 图片（≤3 张 ≤5MB）+ 可选诊断信息。
/// 错误以字符串 code 返回：NOT_LOGGED_IN / FEEDBACK_RATE_LIMITED / HTTP_xxx / 网络错误描述。
#[tauri::command]
pub async fn submit_feedback(
    app: AppHandle,
    content: String,
    images: Vec<FeedbackImage>,
    include_diagnostics: bool,
) -> Result<Value, String> {
    let (url, token) = base(&app);
    if token.is_empty() {
        return Err("NOT_LOGGED_IN".into());
    }
    let app_version = app.package_info().version.to_string();

    let mut form = reqwest::multipart::Form::new().text("content", content);
    for img in images.into_iter().take(3) {
        use base64::Engine;
        let bytes = base64::engine::general_purpose::STANDARD
            .decode(img.data.as_bytes())
            .map_err(|e| e.to_string())?;
        let part = reqwest::multipart::Part::bytes(bytes)
            .mime_str(image_mime(&img.name))
            .map_err(|e| e.to_string())?
            .file_name(img.name);
        form = form.part("images[]", part);
    }
    if include_diagnostics {
        let s = app.state::<crate::settings::SettingsStore>().get();
        let diag = json!({
            "app_version": app_version,
            "platform": PLATFORM,
            "os_version": os_version(),
            "device_id": s.device_id,
        });
        form = form.text("diagnostics", diag.to_string());
    }

    let resp = http()
        .post(format!("{url}/v1/feedback"))
        .bearer_auth(token)
        .header("X-Platform", PLATFORM)
        .header("X-App-Version", app_version)
        .multipart(form)
        .send()
        .await
        .map_err(|e| e.to_string())?;
    match resp.status().as_u16() {
        200 => resp.json().await.map_err(|e| e.to_string()),
        429 => Err("FEEDBACK_RATE_LIMITED".into()),
        s => Err(format!("HTTP_{s}")),
    }
}

// ─── 窗口控制 ────────────────────────────────────────────────────────────────

#[tauri::command]
pub fn open_settings(app: AppHandle) {
    if let Some(w) = app.get_webview_window("settings") {
        let _ = w.show();
        let _ = w.set_focus();
    }
}

#[tauri::command]
pub fn open_login(app: AppHandle) {
    if let Some(w) = app.get_webview_window("login") {
        let _ = w.show();
        let _ = w.set_focus();
    }
}

#[tauri::command]
pub fn close_login(app: AppHandle) {
    if let Some(w) = app.get_webview_window("login") {
        let _ = w.hide();
    }
}

#[tauri::command]
pub fn open_feedback(app: AppHandle) {
    if let Some(w) = app.get_webview_window("feedback") {
        let _ = w.show();
        let _ = w.set_focus();
    }
}

#[tauri::command]
pub fn close_feedback(app: AppHandle) {
    if let Some(w) = app.get_webview_window("feedback") {
        let _ = w.hide();
    }
}

/// 完成首次启动引导（11F）：置 onboarding_done 并关闭引导窗。
#[tauri::command]
pub fn finish_onboarding(app: AppHandle) {
    let store = app.state::<crate::settings::SettingsStore>();
    let mut s = store.get();
    if !s.onboarding_done {
        s.onboarding_done = true;
        store.set(s);
    }
    if let Some(w) = app.get_webview_window("onboarding") {
        let _ = w.hide();
    }
}

/// 比较语义化版本：a 是否严格大于 b（仅按 . 分段的数字比较，非数字段视为 0）。
/// 用于避免把降级（服务端版本反而更旧）误判为可更新（18H）。
fn version_gt(a: &str, b: &str) -> bool {
    let parse = |s: &str| -> Vec<u64> {
        s.split('.')
            .map(|seg| {
                seg.chars()
                    .take_while(|c| c.is_ascii_digit())
                    .collect::<String>()
                    .parse::<u64>()
                    .unwrap_or(0)
            })
            .collect()
    };
    let (va, vb) = (parse(a), parse(b));
    let n = va.len().max(vb.len());
    for i in 0..n {
        let x = va.get(i).copied().unwrap_or(0);
        let y = vb.get(i).copied().unwrap_or(0);
        if x != y {
            return x > y;
        }
    }
    false
}

/// 检查更新：GET /v1/app/latest。返回结果供 UI 提示；force 时走强制更新路径。
#[tauri::command]
pub async fn check_update(app: AppHandle) -> Option<Value> {
    let current = app.package_info().version.to_string();
    let latest = get_json(&app, &format!("/v1/app/latest?platform={PLATFORM}"), false).await?;
    let version = latest.get("version").and_then(|v| v.as_str()).unwrap_or_default();
    let dl = latest.get("url").and_then(|v| v.as_str()).unwrap_or_default();
    let force = latest.get("force").and_then(|v| v.as_bool()).unwrap_or(false);
    // 仅当服务端版本严格高于当前版本才算可更新；降级/同版不提示（18H）。
    let available = !version.is_empty() && version_gt(version, &current);
    // 普通可更新：自动打开下载页。强制更新：不自动打开，交由 UI 走强制更新提示路径，
    // 避免与普通更新等同（用户可能被强制弹窗拦截，需 UI 显式引导）。
    if available && !force && !dl.is_empty() {
        open_external(dl);
    }
    Some(json!({
        "current": current,
        "latest": version,
        "update_available": available,
        "force": force && available,
        "url": dl,
    }))
}

/// 用系统默认方式打开外部链接。
fn open_external(url: &str) {
    #[cfg(target_os = "macos")]
    let _ = std::process::Command::new("open").arg(url).spawn();
    #[cfg(target_os = "windows")]
    let _ = std::process::Command::new("cmd").args(["/C", "start", "", url]).spawn();
    #[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
    let _ = std::process::Command::new("xdg-open").arg(url).spawn();
}

#[tauri::command]
pub fn quit_app(app: AppHandle) {
    app.exit(0);
}
