//! 微信 ClawBot 对接：扫码登录、长轮询收消息、主动发通知/回复，以及额度告警。
//!
//! 设计与现有模块对齐：网络/阻塞型命令标 `#[tauri::command(async)]`（不冻 UI 线程）；
//! 长轮询/额度检查跑在独立 std 线程（无 tokio）；凭据单独落盘（见 store.rs）。
//! 出站通知由前端组装好文案后调 `wechat_send_notice`，发给"操作人"（已激活的微信用户）。

mod api;
mod store;

use serde::Serialize;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tauri::{AppHandle, Emitter, Manager, State};

use crate::AppState;
use store::Creds;

/// 登录流程的瞬时状态（仅未绑定时用；已绑定后 phase 由 wechat_status 据凭据派生为 bound）
#[derive(Debug, Clone, Default)]
struct LoginState {
    /// "" / await_scan / scanned / expired / error
    phase: String,
    /// 当前二维码内容串（轮询状态用）
    qrcode: String,
    /// 二维码 data URL（前端 <img> 用）
    qr_data_url: String,
    /// 最近一次提示/错误
    message: String,
}

struct Inner {
    creds: Option<Creds>,
    /// user_id -> 最新 context_token（回复必需，每收一条消息刷新；进程内，不持久化）
    ctx_tokens: HashMap<String, String>,
    login: LoginState,
}

/// 微信对接状态：作为 Tauri 托管状态。线程用"代际"控制：启动/停止/解绑递增代际，
/// 旧线程检测到代际变化即退出（长轮询因 hold 最长 ~45s 后才感知，可接受）。
pub struct WechatState {
    inner: Arc<Mutex<Inner>>,
    generation: Arc<AtomicU64>,
    /// 额度告警去重：tier 名 -> 是否已告警（利用率跌回 80% 以下重置）
    quota_alerted: Arc<Mutex<HashMap<String, bool>>>,
}

impl Default for WechatState {
    fn default() -> Self {
        Self::new()
    }
}

impl WechatState {
    pub fn new() -> Self {
        Self {
            inner: Arc::new(Mutex::new(Inner {
                creds: store::load(),
                ctx_tokens: HashMap::new(),
                login: LoginState::default(),
            })),
            generation: Arc::new(AtomicU64::new(0)),
            quota_alerted: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    fn bump_gen(&self) -> u64 {
        self.generation.fetch_add(1, Ordering::SeqCst) + 1
    }

    /// 启动长轮询与额度告警线程（需已有凭据）。会递增代际以取消旧线程。
    pub fn start_session(app: &AppHandle) {
        let st = app.state::<WechatState>();
        if st.inner.lock().unwrap().creds.is_none() {
            return;
        }
        let gen = st.bump_gen();
        spawn_poller(app.clone(), gen);
        spawn_quota_watch(app.clone(), gen);
    }

    /// 停止所有后台线程（不清除凭据）。
    pub fn stop_session(app: &AppHandle) {
        app.state::<WechatState>().bump_gen();
    }
}

fn settings_of(app: &AppHandle) -> crate::store::Settings {
    app.state::<AppState>().config.lock().unwrap().settings.clone()
}

fn tier_label(name: &str) -> String {
    match name {
        "five_hour" => "5 小时窗口".into(),
        "seven_day" => "7 天窗口".into(),
        "seven_day_opus" => "7 天 Opus".into(),
        "seven_day_sonnet" => "7 天 Sonnet".into(),
        other => other.to_string(),
    }
}

// ---------- 后台线程 ----------

/// 扫码登录轮询：每 2s 查一次状态，confirmed 即落盘并启动长轮询。
fn spawn_login_poll(app: AppHandle, gen: u64, mut base: String, mut qrcode: String) {
    let _ = std::thread::Builder::new()
        .name("ccgui-wechat-login".into())
        .spawn(move || {
            let st = app.state::<WechatState>();
            let mut refreshes = 0u32;
            let mut errors = 0u32;
            loop {
                if st.generation.load(Ordering::SeqCst) != gen {
                    return;
                }
                std::thread::sleep(Duration::from_secs(2));
                if st.generation.load(Ordering::SeqCst) != gen {
                    return;
                }
                match api::get_qrcode_status(&base, &qrcode) {
                    Ok(s) => {
                        errors = 0;
                        let confirmed = s.status.contains("confirm") || !s.bot_token.is_empty();
                        if confirmed {
                            let creds = Creds {
                                token: s.bot_token,
                                base_url: if s.base_url.is_empty() { base.clone() } else { s.base_url },
                                account_id: s.account_id,
                                nick: s.nick,
                                cursor: String::new(),
                                operator: None,
                            };
                            let _ = store::save(&creds);
                            {
                                let mut inner = st.inner.lock().unwrap();
                                inner.creds = Some(creds);
                                inner.login = LoginState {
                                    phase: "bound".into(),
                                    ..Default::default()
                                };
                            }
                            WechatState::start_session(&app);
                            return;
                        }
                        if s.status == "expired" {
                            if refreshes >= 3 {
                                set_login(&st, |l| {
                                    l.phase = "expired".into();
                                    l.message = "二维码已过期，请重新获取".into();
                                });
                                return;
                            }
                            refreshes += 1;
                            match api::get_bot_qrcode(&base) {
                                Ok(info) => {
                                    qrcode = info.qrcode.clone();
                                    let url = api::qr_to_data_url(&info).unwrap_or_default();
                                    set_login(&st, |l| {
                                        l.phase = "await_scan".into();
                                        l.qrcode = qrcode.clone();
                                        l.qr_data_url = url.clone();
                                        l.message.clear();
                                    });
                                }
                                Err(e) => {
                                    set_login(&st, |l| {
                                        l.phase = "error".into();
                                        l.message = e.clone();
                                    });
                                    return;
                                }
                            }
                            continue;
                        }
                        // 需要切 IDC 基址：后续请求换用返回的 host
                        if !s.redirect_host.is_empty() {
                            base = format!("https://{}", s.redirect_host);
                        }
                        if s.status.starts_with("scaned") || s.status.starts_with("scanned") {
                            set_login(&st, |l| l.phase = "scanned".into());
                        }
                    }
                    Err(e) => {
                        // get_qrcode_status 是长轮询，偶发超时/网络抖动不应直接放弃；
                        // 连续多次失败才退出，期间仅更新提示。
                        errors += 1;
                        set_login(&st, |l| l.message = format!("扫码状态查询出错（{errors}/5）: {e}"));
                        if errors >= 5 {
                            set_login(&st, |l| l.phase = "error".into());
                            return;
                        }
                    }
                }
            }
        });
}

/// 长轮询收消息：getupdates（hold ~35s）→ 刷新游标/context_token → emit 给前端。
fn spawn_poller(app: AppHandle, gen: u64) {
    let _ = std::thread::Builder::new()
        .name("ccgui-wechat-poll".into())
        .spawn(move || {
            let st = app.state::<WechatState>();
            let mut backoff = 1u64;
            loop {
                if st.generation.load(Ordering::SeqCst) != gen {
                    return;
                }
                let (base, token, cursor) = {
                    let inner = st.inner.lock().unwrap();
                    match &inner.creds {
                        Some(c) => (c.base_url.clone(), c.token.clone(), c.cursor.clone()),
                        None => return,
                    }
                };
                match api::get_updates(&base, &token, &cursor) {
                    Ok(resp) => {
                        backoff = 1;
                        // 游标推进并持久化（去重唯一手段）
                        if !resp.cursor.is_empty() {
                            let mut inner = st.inner.lock().unwrap();
                            if let Some(c) = inner.creds.as_mut() {
                                c.cursor = resp.cursor.clone();
                            }
                            if let Some(c) = inner.creds.clone() {
                                let _ = store::save(&c);
                            }
                        }
                        if resp.msgs.is_empty() {
                            continue;
                        }
                        let remote_input = settings_of(&app).wechat_remote_input;
                        for msg in resp.msgs {
                            {
                                let mut inner = st.inner.lock().unwrap();
                                inner
                                    .ctx_tokens
                                    .insert(msg.from_user_id.clone(), msg.context_token.clone());
                                let changed = inner
                                    .creds
                                    .as_ref()
                                    .and_then(|c| c.operator.clone())
                                    != Some(msg.from_user_id.clone());
                                if let Some(c) = inner.creds.as_mut() {
                                    c.operator = Some(msg.from_user_id.clone());
                                }
                                if changed {
                                    if let Some(c) = inner.creds.clone() {
                                        let _ = store::save(&c);
                                    }
                                }
                            }
                            // 图片消息：下载+解密+落盘，得到本地路径；失败则回执并跳过
                            let image_path = if let Some(img) = &msg.image {
                                match download_and_save_image(img) {
                                    Ok(p) => p,
                                    Err(e) => {
                                        let _ = api::send_message(
                                            &base,
                                            &token,
                                            &msg.from_user_id,
                                            &msg.context_token,
                                            &format!("[CCGUI] 图片接收失败: {e}"),
                                        );
                                        continue;
                                    }
                                }
                            } else {
                                String::new()
                            };
                            // 仅在允许遥控时把消息交给前端路由
                            if remote_input {
                                let _ = app.emit(
                                    "wechat-inbound",
                                    InboundPayload {
                                        user_id: msg.from_user_id.clone(),
                                        text: msg.text.clone(),
                                        image_path,
                                    },
                                );
                            }
                        }
                    }
                    Err(e) => {
                        set_login(&st, |l| l.message = format!("收消息错误: {e}"));
                        sleep_check(&st, gen, backoff);
                        backoff = (backoff * 2).min(10);
                    }
                }
            }
        });
}

/// 额度告警：每 5 分钟查一次账号额度，窗口利用率越过阈值时推送给操作人。
fn spawn_quota_watch(app: AppHandle, gen: u64) {
    let _ = std::thread::Builder::new()
        .name("ccgui-wechat-quota".into())
        .spawn(move || {
            let st = app.state::<WechatState>();
            loop {
                // 5 分钟一轮，分段 sleep 以便解绑/关闭后及时退出
                for _ in 0..60 {
                    if st.generation.load(Ordering::SeqCst) != gen {
                        return;
                    }
                    std::thread::sleep(Duration::from_secs(5));
                }
                let s = settings_of(&app);
                if !s.wechat_enabled || !s.wechat_push_quota {
                    continue;
                }
                let (operator, base, token, ctx) = {
                    let inner = st.inner.lock().unwrap();
                    let op = inner.creds.as_ref().and_then(|c| c.operator.clone());
                    let base = inner.creds.as_ref().map(|c| c.base_url.clone()).unwrap_or_default();
                    let token = inner.creds.as_ref().map(|c| c.token.clone()).unwrap_or_default();
                    let ctx = op.as_ref().and_then(|o| inner.ctx_tokens.get(o).cloned());
                    (op, base, token, ctx)
                };
                let (Some(operator), Some(ctx)) = (operator, ctx) else {
                    continue;
                };
                let q = crate::quota::query_account_quota();
                if q.status != "ok" {
                    continue;
                }
                let mut lines = Vec::new();
                {
                    let mut alerted = st.quota_alerted.lock().unwrap();
                    for t in &q.tiers {
                        let was = *alerted.get(&t.name).unwrap_or(&false);
                        if t.utilization >= 90.0 && !was {
                            alerted.insert(t.name.clone(), true);
                            lines.push(format!("{}：已用 {:.0}%", tier_label(&t.name), t.utilization));
                        } else if t.utilization < 80.0 && was {
                            alerted.insert(t.name.clone(), false);
                        }
                    }
                }
                if !lines.is_empty() {
                    let text = format!("[CCGUI] 额度告警\n{}", lines.join("\n"));
                    let _ = api::send_message(&base, &token, &operator, &ctx, &text);
                }
            }
        });
}

fn set_login(st: &WechatState, f: impl FnOnce(&mut LoginState)) {
    f(&mut st.inner.lock().unwrap().login);
}

/// 下载并解密一张微信图片，按魔数判后缀落盘到 %TEMP%/ccgui，返回本地路径。
fn download_and_save_image(img: &api::ImageRef) -> Result<String, String> {
    let bytes = api::download_image(img)?;
    let ext = if bytes.starts_with(&[0x89, 0x50, 0x4E, 0x47]) {
        "png"
    } else if bytes.starts_with(&[0xFF, 0xD8, 0xFF]) {
        "jpg"
    } else if bytes.starts_with(b"GIF8") {
        "gif"
    } else if bytes.starts_with(b"RIFF") {
        "webp"
    } else {
        "bin"
    };
    let dir = std::env::temp_dir().join("ccgui");
    std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
    let path = dir.join(format!("wx-{}.{ext}", crate::store::now_ms()));
    std::fs::write(&path, &bytes).map_err(|e| e.to_string())?;
    Ok(path.to_string_lossy().into_owned())
}

/// 分段 sleep（每秒检查代际），用于退避时也能及时响应停止
fn sleep_check(st: &WechatState, gen: u64, secs: u64) {
    for _ in 0..secs {
        if st.generation.load(Ordering::SeqCst) != gen {
            return;
        }
        std::thread::sleep(Duration::from_secs(1));
    }
}

// ---------- 事件 / 状态结构 ----------

#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct InboundPayload {
    user_id: String,
    text: String,
    /// 图片消息时为已下载落盘的本地路径（前端插入终端输入框）
    image_path: String,
}

#[derive(Clone, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct WechatStatus {
    pub enabled: bool,
    /// off / await_scan / scanned / bound / expired / error
    pub phase: String,
    pub bound: bool,
    pub nick: String,
    /// 操作人已激活（已有 context_token，可推送/回复）
    pub operator_active: bool,
    pub qr_data_url: String,
    pub message: String,
}

// ---------- Tauri 命令 ----------

#[tauri::command]
pub fn wechat_status(app: AppHandle, state: State<WechatState>) -> WechatStatus {
    let enabled = app
        .state::<AppState>()
        .config
        .lock()
        .unwrap()
        .settings
        .wechat_enabled;
    let inner = state.inner.lock().unwrap();
    let bound = inner.creds.is_some();
    let nick = inner.creds.as_ref().map(|c| c.nick.clone()).unwrap_or_default();
    let operator_active = inner
        .creds
        .as_ref()
        .and_then(|c| c.operator.clone())
        .map(|op| inner.ctx_tokens.contains_key(&op))
        .unwrap_or(false);
    let phase = if bound {
        "bound".to_string()
    } else if inner.login.phase.is_empty() {
        "off".to_string()
    } else {
        inner.login.phase.clone()
    };
    WechatStatus {
        enabled,
        phase,
        bound,
        nick,
        operator_active,
        qr_data_url: if bound {
            String::new()
        } else {
            inner.login.qr_data_url.clone()
        },
        message: inner.login.message.clone(),
    }
}

/// 开始扫码登录：取二维码并起轮询线程，返回二维码 data URL 供前端立即显示。
// 标注 (async)：内部 get_bot_qrcode 为网络请求，移出主线程
#[tauri::command(async)]
pub fn wechat_start_login(app: AppHandle, state: State<WechatState>) -> Result<String, String> {
    // 取消任何进行中的会话/登录
    let gen = state.bump_gen();
    let info = api::get_bot_qrcode("")?;
    let url = api::qr_to_data_url(&info)?;
    {
        let mut inner = state.inner.lock().unwrap();
        inner.login = LoginState {
            phase: "await_scan".into(),
            qrcode: info.qrcode.clone(),
            qr_data_url: url.clone(),
            message: String::new(),
        };
    }
    spawn_login_poll(app, gen, String::new(), info.qrcode);
    Ok(url)
}

/// 解绑：停线程、删凭据、清状态。
#[tauri::command]
pub fn wechat_unbind(state: State<WechatState>) {
    state.bump_gen();
    store::clear();
    let mut inner = state.inner.lock().unwrap();
    inner.creds = None;
    inner.ctx_tokens.clear();
    inner.login = LoginState::default();
    state.quota_alerted.lock().unwrap().clear();
}

/// 给操作人发一条消息（出站通知/回复）。未绑定或操作人未激活时静默跳过。
// 标注 (async)：网络请求，移出主线程
#[tauri::command(async)]
pub fn wechat_send_notice(state: State<WechatState>, text: String) -> Result<(), String> {
    let (base, token, operator, ctx) = {
        let inner = state.inner.lock().unwrap();
        let Some(c) = inner.creds.as_ref() else {
            return Ok(());
        };
        let Some(op) = c.operator.clone() else {
            return Ok(());
        };
        let Some(ctx) = inner.ctx_tokens.get(&op).cloned() else {
            return Ok(());
        };
        (c.base_url.clone(), c.token.clone(), op, ctx)
    };
    api::send_message(&base, &token, &operator, &ctx, &text)
}
