use crate::db;
use crate::models::GitAccount;
use once_cell::sync::Lazy;
use std::io::{Read, Write};
use std::net::TcpListener;
use std::sync::{Arc, Mutex};
use uuid::Uuid;

// ── OAuth Authorization Code Flow 共享状态 ────────────────────────────────────

static OAUTH_RESULT: Lazy<Arc<Mutex<Option<Result<String, String>>>>> =
    Lazy::new(|| Arc::new(Mutex::new(None)));

fn read_setting(key: &str) -> String {
    let conn = match db::pool().get() {
        Ok(c) => c,
        Err(_) => return String::new(),
    };
    conn.query_row(
        "SELECT value FROM settings WHERE key = ?1",
        rusqlite::params![key],
        |r| r.get::<_, String>(0),
    )
    .unwrap_or_default()
}

/// URL 编码（不依赖外部 crate）
fn url_encode(s: &str) -> String {
    let mut out = String::new();
    for byte in s.as_bytes() {
        match byte {
            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9'
            | b'-' | b'_' | b'.' | b'~' => out.push(*byte as char),
            b => out.push_str(&format!("%{:02X}", b)),
        }
    }
    out
}

/// 用 ureq 把 code 换成 access_token
fn exchange_code(client_id: &str, client_secret: &str, code: &str, port: u16)
    -> Result<String, String>
{
    let redirect_uri = format!("http://localhost:{}/callback", port);
    let body = format!(
        "client_id={}&client_secret={}&code={}&redirect_uri={}",
        url_encode(client_id),
        url_encode(client_secret),
        url_encode(code),
        url_encode(&redirect_uri),
    );

    let resp = ureq::post("https://github.com/login/oauth/access_token")
        .set("Accept", "application/json")
        .set("Content-Type", "application/x-www-form-urlencoded")
        .send_string(&body)
        .map_err(|e| format!("网络请求失败: {e}"))?;

    let json: serde_json::Value = resp.into_json()
        .map_err(|e| format!("解析响应失败: {e}"))?;

    if let Some(token) = json["access_token"].as_str() {
        return Ok(token.to_string());
    }
    Err(json["error_description"]
        .as_str()
        .or_else(|| json["error"].as_str())
        .unwrap_or("token 交换失败")
        .to_string())
}

/// 启动 Authorization Code Flow。
/// 绑定随机端口，后台等待浏览器回调，返回 GitHub 授权 URL。
#[tauri::command]
pub fn github_start_oauth() -> Result<String, String> {
    let client_id = read_setting("github_client_id");
    let client_secret = read_setting("github_client_secret");

    if client_id.trim().is_empty() {
        return Err("未配置 GitHub Client ID，请在设置中填写".to_string());
    }
    if client_secret.trim().is_empty() {
        return Err("未配置 GitHub Client Secret，请在设置中填写".to_string());
    }

    // 绑定随机可用端口
    let listener = TcpListener::bind("127.0.0.1:0")
        .map_err(|e| format!("无法绑定本地端口: {e}"))?;
    let port = listener.local_addr().unwrap().port();

    // 重置上一次结果
    *OAUTH_RESULT.lock().unwrap() = None;

    let client_id_c = client_id.clone();
    let client_secret_c = client_secret.clone();

    std::thread::spawn(move || {
        if let Ok((mut stream, _)) = listener.accept() {
            let mut buf = vec![0u8; 8192];
            let n = stream.read(&mut buf).unwrap_or(0);
            let request = String::from_utf8_lossy(&buf[..n]).to_string();

            // 从 "GET /callback?code=xxx HTTP/1.1" 提取 code
            let code = request.lines().next().and_then(|line| {
                let path = line.split_whitespace().nth(1)?;
                let query = path.split('?').nth(1)?;
                query
                    .split('&')
                    .find(|seg| seg.starts_with("code="))
                    .map(|seg| seg[5..].to_string())
            });

            let (status, html, result) = match code {
                Some(code) => match exchange_code(&client_id_c, &client_secret_c, &code, port) {
                    Ok(token) => (
                        "200 OK",
                        "<html><head><meta charset='utf-8'></head><body style='font-family:sans-serif;text-align:center;padding:80px;background:#f6f8fa'><h2 style='color:#1a7f37'>✅ 授权成功</h2><p style='color:#57606a'>可以关闭此标签页，返回应用继续操作。</p></body></html>",
                        Ok(token),
                    ),
                    Err(e) => (
                        "400 Bad Request",
                        "<html><head><meta charset='utf-8'></head><body style='font-family:sans-serif;text-align:center;padding:80px'><h2 style='color:#d1242f'>❌ 授权失败</h2><p>请返回应用重试。</p></body></html>",
                        Err(e),
                    ),
                },
                None => (
                    "400 Bad Request",
                    "<html><head><meta charset='utf-8'></head><body style='font-family:sans-serif;text-align:center;padding:80px'><h2 style='color:#d1242f'>❌ 未收到授权码</h2></body></html>",
                    Err("GitHub 未返回授权码".to_string()),
                ),
            };

            let response = format!(
                "HTTP/1.1 {status}\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{html}",
                html.len()
            );
            let _ = stream.write_all(response.as_bytes());
            *OAUTH_RESULT.lock().unwrap() = Some(result);
        }
    });

    let redirect_uri = format!("http://localhost:{}/callback", port);
    Ok(format!(
        "https://github.com/login/oauth/authorize?client_id={}&redirect_uri={}&scope=repo+user&allow_signup=false",
        url_encode(&client_id),
        url_encode(&redirect_uri),
    ))
}

/// 前端轮询：None = 等待中，Some(token) = 成功，Err = 失败
#[tauri::command]
pub fn github_poll_oauth() -> Result<Option<String>, String> {
    match &*OAUTH_RESULT.lock().unwrap() {
        None => Ok(None),
        Some(Ok(token)) => Ok(Some(token.clone())),
        Some(Err(e)) => Err(e.clone()),
    }
}

/// 检测 github.com:443 是否可达（3 秒超时）
fn check_github_reachable() -> bool {
    use std::net::{TcpStream, ToSocketAddrs};
    use std::time::Duration;
    let Ok(addrs) = "github.com:443".to_socket_addrs() else {
        return false;
    };
    addrs
        .into_iter()
        .any(|addr| TcpStream::connect_timeout(&addr, Duration::from_secs(3)).is_ok())
}

/// 用系统默认浏览器打开 GitHub 授权页，TcpListener 接收回调（Authorization Code Flow）
/// 使用系统浏览器避免内嵌 WebView2 的代理/渲染问题
#[tauri::command]
pub fn github_open_login_window(_app: tauri::AppHandle) -> Result<(), String> {
    if !check_github_reachable() {
        return Err("无法连接 GitHub（github.com:443 超时），请先确认代理已开启".to_string());
    }

    let client_id = read_setting("github_client_id");
    let client_secret = read_setting("github_client_secret");

    if client_id.trim().is_empty() {
        return Err("未配置 GitHub Client ID，请在设置中填写".to_string());
    }
    if client_secret.trim().is_empty() {
        return Err("未配置 GitHub Client Secret，请在设置中填写".to_string());
    }

    *OAUTH_RESULT.lock().unwrap() = None;

    // 绑定随机本地端口接收 GitHub 回调
    let listener = TcpListener::bind("127.0.0.1:0")
        .map_err(|e| format!("无法绑定本地端口: {e}"))?;
    let port = listener.local_addr().unwrap().port();

    let redirect_uri = format!("http://localhost:{}/callback", port);
    let auth_url = format!(
        "https://github.com/login/oauth/authorize?client_id={}&redirect_uri={}&scope=repo+user&allow_signup=false",
        url_encode(&client_id),
        url_encode(&redirect_uri),
    );

    // 后台线程：等待 GitHub 回调，换取 token
    std::thread::spawn(move || {
        if let Ok((mut stream, _)) = listener.accept() {
            let mut buf = vec![0u8; 8192];
            let n = stream.read(&mut buf).unwrap_or(0);
            let request = String::from_utf8_lossy(&buf[..n]).to_string();

            let code = request.lines().next().and_then(|line| {
                let path = line.split_whitespace().nth(1)?;
                let query = path.split('?').nth(1)?;
                query
                    .split('&')
                    .find(|s| s.starts_with("code="))
                    .map(|s| s[5..].to_string())
            });

            let (status, html, result) = match code {
                Some(code) => match exchange_code(&client_id, &client_secret, &code, port) {
                    Ok(token) => (
                        "200 OK",
                        "<html><head><meta charset='utf-8'></head><body style='font-family:sans-serif;text-align:center;padding:80px;background:#f6f8fa'><h2 style='color:#1a7f37'>✅ 授权成功</h2><p style='color:#57606a'>可以关闭此标签页，返回应用继续操作。</p></body></html>",
                        Ok(token),
                    ),
                    Err(e) => (
                        "400 Bad Request",
                        "<html><head><meta charset='utf-8'></head><body style='font-family:sans-serif;text-align:center;padding:80px'><h2 style='color:#d1242f'>❌ 授权失败</h2><p>请返回应用重试。</p></body></html>",
                        Err(e),
                    ),
                },
                None => (
                    "400 Bad Request",
                    "<html><head><meta charset='utf-8'></head><body style='font-family:sans-serif;text-align:center;padding:80px'><h2 style='color:#d1242f'>❌ 未收到授权码</h2></body></html>",
                    Err("GitHub 未返回授权码".to_string()),
                ),
            };

            let response = format!(
                "HTTP/1.1 {status}\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{html}",
                html.len()
            );
            let _ = stream.write_all(response.as_bytes());
            *OAUTH_RESULT.lock().unwrap() = Some(result);
        }
    });

    // 用系统默认浏览器打开（走系统代理，不受 WebView2 限制）
    open::that(&auth_url).map_err(|e| format!("无法打开浏览器: {e}"))?;

    Ok(())
}

/// 保存 GitHub 账户信息和 token（登录成功后由前端调用）
#[tauri::command]
pub fn github_save_account(
    username: String,
    avatar_url: String,
    access_token: String,
) -> Result<GitAccount, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;

    // 已存在则复用同一个 id
    let existing_id: Option<String> = conn
        .query_row(
            "SELECT id FROM git_accounts WHERE username = ?1",
            rusqlite::params![username],
            |row| row.get(0),
        )
        .ok();

    let id = existing_id.unwrap_or_else(|| Uuid::new_v4().to_string());

    conn.execute(
        "INSERT INTO git_accounts (id, username, avatar_url, provider)
         VALUES (?1, ?2, ?3, 'github')
         ON CONFLICT(username) DO UPDATE SET avatar_url = excluded.avatar_url",
        rusqlite::params![id, username, avatar_url],
    )
    .map_err(|e| e.to_string())?;

    // token 存入 settings 表
    conn.execute(
        "INSERT OR REPLACE INTO settings (key, value) VALUES ('github_token', ?1)",
        rusqlite::params![access_token],
    )
    .map_err(|e| e.to_string())?;

    let account = conn
        .query_row(
            "SELECT id, username, avatar_url, provider, created_at FROM git_accounts WHERE id = ?1",
            rusqlite::params![id],
            GitAccount::from_row,
        )
        .map_err(|e| e.to_string())?;

    Ok(account)
}

/// 获取当前已登录账户（无则返回 None）
#[tauri::command]
pub fn github_get_account() -> Result<Option<GitAccount>, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    match conn.query_row(
        "SELECT id, username, avatar_url, provider, created_at FROM git_accounts LIMIT 1",
        [],
        GitAccount::from_row,
    ) {
        Ok(account) => Ok(Some(account)),
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
        Err(e) => Err(e.to_string()),
    }
}

/// 获取已存储的 access_token（前端调用 GitHub API 时使用）
#[tauri::command]
pub fn github_get_token() -> Result<Option<String>, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    match conn.query_row(
        "SELECT value FROM settings WHERE key = 'github_token'",
        [],
        |row| row.get::<_, String>(0),
    ) {
        Ok(token) if !token.is_empty() => Ok(Some(token)),
        _ => Ok(None),
    }
}

/// 获取 GitHub Client ID（前端发起 Device Flow 时使用）
#[tauri::command]
pub fn github_get_client_id() -> Result<String, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    let value: String = conn
        .query_row(
            "SELECT value FROM settings WHERE key = 'github_client_id'",
            [],
            |row| row.get(0),
        )
        .unwrap_or_default();
    if value.trim().is_empty() {
        return Err("未配置 GitHub Client ID，请在「设置」页面填写后再登录".to_string());
    }
    Ok(value)
}

/// 登出：清除账户和 token
#[tauri::command]
pub fn github_logout() -> Result<(), String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    conn.execute("DELETE FROM git_accounts", [])
        .map_err(|e| e.to_string())?;
    conn.execute(
        "DELETE FROM settings WHERE key = 'github_token'",
        [],
    )
    .map_err(|e| e.to_string())?;
    Ok(())
}
