use serde::Serialize;
use serde_json::Value;
use std::fs;
use std::io::Read;
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionInfo {
    pub id: String,
    pub title: String,
    pub mtime: u64,
    pub size: u64,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PreviewMessage {
    pub role: String,
    pub text: String,
}

#[derive(Debug, Clone, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct UsageBucket {
    pub input_tokens: u64,
    pub output_tokens: u64,
    pub cache_read_tokens: u64,
    pub cache_creation_tokens: u64,
    pub cost_usd: f64,
}

#[derive(Debug, Clone, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct UsageStats {
    pub day: UsageBucket,
    pub week: UsageBucket,
    pub month: UsageBucket,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchHit {
    pub project_path: String,
    pub session_id: String,
    pub title: String,
    pub snippet: String,
    pub mtime: u64,
}

/// Claude Code 将项目路径中所有非字母数字字符替换为 '-' 作为存储目录名
/// 例如 F:\CCGUI -> F--CCGUI
pub fn encode_project_path(project_path: &str) -> String {
    project_path
        .chars()
        .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
        .collect()
}

fn sessions_dir(project_path: &str) -> PathBuf {
    dirs::home_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join(".claude")
        .join("projects")
        .join(encode_project_path(project_path))
}

fn session_file(project_path: &str, session_id: &str) -> PathBuf {
    sessions_dir(project_path).join(format!("{session_id}.jsonl"))
}

/// 从 JSONL 行的 content 字段提取文本（可能是字符串或内容块数组）
fn extract_text(content: &Value) -> Option<String> {
    match content {
        Value::String(s) => Some(s.clone()),
        Value::Array(blocks) => {
            for block in blocks {
                if block.get("type").and_then(Value::as_str) == Some("text") {
                    if let Some(t) = block.get("text").and_then(Value::as_str) {
                        return Some(t.to_string());
                    }
                }
            }
            None
        }
        _ => None,
    }
}

/// 读取文件头部若干字节，提取会话标题：优先 summary 行，其次首条真实用户消息
pub fn parse_title(path: &PathBuf) -> Option<String> {
    let mut file = fs::File::open(path).ok()?;
    let mut buf = vec![0u8; 256 * 1024];
    let n = file.read(&mut buf).ok()?;
    let text = String::from_utf8_lossy(&buf[..n]);
    let mut first_user_text: Option<String> = None;
    for line in text.split('\n') {
        if line.trim().is_empty() {
            continue;
        }
        let entry: Value = match serde_json::from_str(line) {
            Ok(v) => v,
            Err(_) => continue, // 最后一行可能被截断
        };
        if entry.get("type").and_then(Value::as_str) == Some("summary") {
            if let Some(s) = entry.get("summary").and_then(Value::as_str) {
                return Some(s.to_string());
            }
        }
        if entry.get("type").and_then(Value::as_str) == Some("user")
            && !entry.get("isMeta").and_then(Value::as_bool).unwrap_or(false)
            && first_user_text.is_none()
        {
            if let Some(content) = entry.pointer("/message/content") {
                if let Some(t) = extract_text(content) {
                    // 跳过本地命令、系统注入等非用户输入
                    if !t.starts_with('<') {
                        let collapsed: String = t.split_whitespace().collect::<Vec<_>>().join(" ");
                        first_user_text = Some(collapsed.chars().take(80).collect());
                    }
                }
            }
        }
    }
    first_user_text
}

/// 移动端「完整历史」用：把会话 transcript 解析成一串可读消息（用户/助手文本 + 工具调用精简行）。
/// 直接读 JSONL 源文件，不依赖终端缓冲，不受 resize/清屏影响。
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryMsg {
    pub role: String, // user | assistant | tool
    pub text: String,
}

pub fn read_history(project_path: &str, session_id: &str) -> Vec<HistoryMsg> {
    let path = session_file(project_path, session_id);
    let raw = match fs::read_to_string(&path) {
        Ok(r) => r,
        Err(_) => return Vec::new(),
    };
    let mut out: Vec<HistoryMsg> = Vec::new();
    for line in raw.lines() {
        if line.trim().is_empty() {
            continue;
        }
        let entry: Value = match serde_json::from_str(line) {
            Ok(v) => v,
            Err(_) => continue,
        };
        if entry.get("isMeta").and_then(Value::as_bool).unwrap_or(false) {
            continue;
        }
        let typ = entry.get("type").and_then(Value::as_str).unwrap_or("");
        let content = match entry.pointer("/message/content") {
            Some(c) => c,
            None => continue,
        };
        match typ {
            "user" => {
                // 只取真实用户文本；工具结果 / 系统注入（<...>）跳过
                if let Some(t) = extract_text(content) {
                    let t = t.trim();
                    if !t.is_empty() && !t.starts_with('<') {
                        out.push(HistoryMsg {
                            role: "user".into(),
                            text: t.to_string(),
                        });
                    }
                }
            }
            "assistant" => {
                if let Some(blocks) = content.as_array() {
                    for b in blocks {
                        match b.get("type").and_then(Value::as_str) {
                            Some("text") => {
                                if let Some(t) = b.get("text").and_then(Value::as_str) {
                                    if !t.trim().is_empty() {
                                        out.push(HistoryMsg {
                                            role: "assistant".into(),
                                            text: t.to_string(),
                                        });
                                    }
                                }
                            }
                            Some("tool_use") => {
                                let name = b.get("name").and_then(Value::as_str).unwrap_or("tool");
                                // 取一个简短参数提示（命令 / 文件 / 模式）
                                let hint = b
                                    .pointer("/input/command")
                                    .or_else(|| b.pointer("/input/file_path"))
                                    .or_else(|| b.pointer("/input/path"))
                                    .or_else(|| b.pointer("/input/pattern"))
                                    .or_else(|| b.pointer("/input/description"))
                                    .and_then(Value::as_str)
                                    .map(|s| {
                                        let brief: String =
                                            s.split_whitespace().collect::<Vec<_>>().join(" ");
                                        format!("  {}", brief.chars().take(80).collect::<String>())
                                    })
                                    .unwrap_or_default();
                                out.push(HistoryMsg {
                                    role: "tool".into(),
                                    text: format!("{}{}", name, hint),
                                });
                            }
                            _ => {}
                        }
                    }
                }
            }
            _ => {}
        }
    }
    // 体积兜底：极长会话只保留最近 1200 条
    if out.len() > 1200 {
        out.drain(..out.len() - 1200);
    }
    out
}

pub fn list_sessions(project_path: &str) -> Vec<SessionInfo> {
    let dir = sessions_dir(project_path);
    let entries = match fs::read_dir(&dir) {
        Ok(e) => e,
        Err(_) => return vec![], // 该项目还没有任何会话记录
    };
    let mut sessions: Vec<SessionInfo> = Vec::new();
    for entry in entries.flatten() {
        let path = entry.path();
        if path.extension().and_then(|e| e.to_str()) != Some("jsonl") {
            continue;
        }
        let Ok(meta) = entry.metadata() else { continue };
        if !meta.is_file() {
            continue;
        }
        let mtime = meta
            .modified()
            .ok()
            .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
            .map(|d| d.as_millis() as u64)
            .unwrap_or(0);
        let id = path
            .file_stem()
            .map(|s| s.to_string_lossy().into_owned())
            .unwrap_or_default();
        let title = parse_title(&path).unwrap_or_else(|| "（空会话）".into());
        sessions.push(SessionInfo {
            id,
            title,
            mtime,
            size: meta.len(),
        });
    }
    sessions.sort_by(|a, b| b.mtime.cmp(&a.mtime));
    sessions
}

/// 完整解析一个会话的对话内容，用于只读预览
pub fn preview_session(project_path: &str, session_id: &str) -> Result<Vec<PreviewMessage>, String> {
    let path = session_file(project_path, session_id);
    let raw = fs::read_to_string(&path).map_err(|e| e.to_string())?;
    let mut messages = Vec::new();
    for line in raw.split('\n') {
        if line.trim().is_empty() {
            continue;
        }
        let entry: Value = match serde_json::from_str(line) {
            Ok(v) => v,
            Err(_) => continue,
        };
        let role = entry.get("type").and_then(Value::as_str).unwrap_or("");
        if role != "user" && role != "assistant" {
            continue;
        }
        if entry.get("isMeta").and_then(Value::as_bool).unwrap_or(false) {
            continue;
        }
        let Some(content) = entry.pointer("/message/content") else {
            continue;
        };
        // 用户消息里可能是工具结果（数组里只有 tool_result 块），提取不到文本就跳过
        if let Some(text) = extract_text(content) {
            if text.trim().is_empty() {
                continue;
            }
            messages.push(PreviewMessage {
                role: role.to_string(),
                text,
            });
        }
        // 列出 assistant 的工具调用名称，预览时能看到执行了什么
        if role == "assistant" {
            if let Some(blocks) = content.as_array() {
                for block in blocks {
                    if block.get("type").and_then(Value::as_str) == Some("tool_use") {
                        if let Some(name) = block.get("name").and_then(Value::as_str) {
                            messages.push(PreviewMessage {
                                role: "tool".into(),
                                text: name.to_string(),
                            });
                        }
                    }
                }
            }
        }
    }
    Ok(messages)
}

pub fn delete_session(project_path: &str, session_id: &str) -> Result<(), String> {
    let path = session_file(project_path, session_id);
    fs::remove_file(&path).map_err(|e| e.to_string())
}

/// 遍历 ~/.claude/projects 下全部会话记录中时间戳不早于 earliest 的 assistant 用量条目。
/// 只读取 mtime 在 earliest 之后的文件：旧文件不可能包含窗口内的新条目。
fn for_each_usage_entry(earliest: u64, mut visit: impl FnMut(u64, &UsageBucket)) {
    let root = dirs::home_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join(".claude")
        .join("projects");
    let Ok(project_dirs) = fs::read_dir(&root) else {
        return;
    };
    for project_dir in project_dirs.flatten() {
        let Ok(files) = fs::read_dir(project_dir.path()) else {
            continue;
        };
        for file in files.flatten() {
            let path = file.path();
            if path.extension().and_then(|e| e.to_str()) != Some("jsonl") {
                continue;
            }
            let mtime_ms = file
                .metadata()
                .ok()
                .and_then(|m| m.modified().ok())
                .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
                .map(|d| d.as_millis() as u64)
                .unwrap_or(0);
            if mtime_ms < earliest {
                continue;
            }
            let Ok(raw) = fs::read_to_string(&path) else {
                continue;
            };
            for line in raw.split('\n') {
                if line.len() < 2 {
                    continue;
                }
                // 快速预筛：只有 assistant 条目才带 usage，避免逐行完整解析
                if !line.contains("\"assistant\"") || !line.contains("\"usage\"") {
                    continue;
                }
                let Ok(entry) = serde_json::from_str::<Value>(line) else {
                    continue;
                };
                if entry.get("type").and_then(Value::as_str) != Some("assistant") {
                    continue;
                }
                let Some(ts) = entry
                    .get("timestamp")
                    .and_then(Value::as_str)
                    .and_then(|s| humantime::parse_rfc3339(s).ok())
                    .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
                    .map(|d| d.as_millis() as u64)
                else {
                    continue;
                };
                if ts < earliest {
                    continue;
                }
                let usage = entry.pointer("/message/usage");
                let field = |name: &str| -> u64 {
                    usage
                        .and_then(|u| u.get(name))
                        .and_then(Value::as_u64)
                        .unwrap_or(0)
                };
                let delta = UsageBucket {
                    input_tokens: field("input_tokens"),
                    output_tokens: field("output_tokens"),
                    cache_read_tokens: field("cache_read_input_tokens"),
                    cache_creation_tokens: field("cache_creation_input_tokens"),
                    // 旧版 Claude Code 在条目上直接写 costUSD，存在则一并累计
                    cost_usd: entry.get("costUSD").and_then(Value::as_f64).unwrap_or(0.0),
                };
                visit(ts, &delta);
            }
        }
    }
}

fn add_bucket(target: &mut UsageBucket, delta: &UsageBucket) {
    target.input_tokens += delta.input_tokens;
    target.output_tokens += delta.output_tokens;
    target.cache_read_tokens += delta.cache_read_tokens;
    target.cache_creation_tokens += delta.cache_creation_tokens;
    target.cost_usd += delta.cost_usd;
}

/// 聚合今日/本周/本月用量；窗口边界由前端按本地时区计算后传入（epoch 毫秒）
pub fn usage_stats(day_start_ms: u64, week_start_ms: u64, month_start_ms: u64) -> UsageStats {
    let earliest = day_start_ms.min(week_start_ms).min(month_start_ms);
    let mut stats = UsageStats::default();
    for_each_usage_entry(earliest, |ts, delta| {
        if ts >= day_start_ms {
            add_bucket(&mut stats.day, delta);
        }
        if ts >= week_start_ms {
            add_bucket(&mut stats.week, delta);
        }
        if ts >= month_start_ms {
            add_bucket(&mut stats.month, delta);
        }
    });
    stats
}

/// 近 N 天逐日用量。返回数组下标 0 为最早一天，今日在末尾。
/// today_start_ms 为今日本地零点；按 86400000ms 等距切分（无夏令时地区准确）。
pub fn usage_trend(today_start_ms: u64, days: u32) -> Vec<UsageBucket> {
    let days = days.clamp(1, 366) as u64;
    let start = today_start_ms.saturating_sub((days - 1) * 86_400_000);
    let mut buckets = vec![UsageBucket::default(); days as usize];
    for_each_usage_entry(start, |ts, delta| {
        let idx = ((ts - start) / 86_400_000) as usize;
        if let Some(b) = buckets.get_mut(idx.min(days as usize - 1)) {
            add_bucket(b, delta);
        }
    });
    buckets
}

#[derive(Debug, Clone, Default)]
pub struct LastAssistant {
    /// 最后一条 assistant 文本（截断 120 字符，作为完成摘要）
    pub text: String,
    /// 最新一条 assistant 消息使用的模型（当前会话实际生效的模型）
    pub model: String,
    /// 最近一个回合内是否出现过 thinking 内容块（扩展思考开启的证据）
    pub thinking: bool,
}

/// 读取会话记录末尾，提取完成摘要、当前模型与思考标记。
/// 扫描范围为最近一个完整回合（倒序读到上一条真实用户消息为止）：
/// thinking 块出现在回合中间的消息里，只看最后一条会漏判。
pub fn last_assistant_info(transcript_path: &str) -> Option<LastAssistant> {
    use std::io::{Read, Seek, SeekFrom};
    let mut file = fs::File::open(transcript_path).ok()?;
    let len = file.metadata().ok()?.len();
    let tail = 64 * 1024;
    let start = len.saturating_sub(tail);
    file.seek(SeekFrom::Start(start)).ok()?;
    let mut bytes = Vec::new();
    std::io::BufReader::new(file).read_to_end(&mut bytes).ok()?;
    // 截断点可能落在多字节字符中间，用有损解码
    let buf = String::from_utf8_lossy(&bytes);
    let mut result = LastAssistant::default();
    for line in buf.split('\n').rev() {
        if line.trim().is_empty() {
            continue;
        }
        let Ok(entry) = serde_json::from_str::<Value>(line) else {
            continue;
        };
        let entry_type = entry.get("type").and_then(Value::as_str).unwrap_or("");
        if entry_type == "user"
            && !entry.get("isMeta").and_then(Value::as_bool).unwrap_or(false)
        {
            // 真实用户消息（非工具结果）= 回合边界，停止扫描
            if entry
                .pointer("/message/content")
                .and_then(extract_text)
                .is_some()
            {
                break;
            }
            continue;
        }
        if entry_type != "assistant" {
            continue;
        }
        // 模型取最新一条 assistant 消息的（可能是纯工具调用条目）。
        // "<synthetic>" 是 CLI 本地合成消息（错误占位等）的标记，不是真实模型，跳过
        if result.model.is_empty() {
            if let Some(m) = entry.pointer("/message/model").and_then(Value::as_str) {
                if m != "<synthetic>" {
                    result.model = m.to_string();
                }
            }
        }
        if let Some(content) = entry.pointer("/message/content") {
            if let Some(blocks) = content.as_array() {
                if blocks
                    .iter()
                    .any(|b| b.get("type").and_then(Value::as_str) == Some("thinking"))
                {
                    result.thinking = true;
                }
            }
            if result.text.is_empty() {
                if let Some(text) = extract_text(content) {
                    let collapsed = text.split_whitespace().collect::<Vec<_>>().join(" ");
                    if !collapsed.is_empty() {
                        result.text = collapsed.chars().take(120).collect();
                    }
                }
            }
        }
    }
    if result.model.is_empty() && result.text.is_empty() {
        None
    } else {
        Some(result)
    }
}

#[derive(Debug, Clone, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct SessionMeta {
    pub model: String,
    pub thinking: bool,
}

/// 读取某个历史会话的模型与思考标记（恢复会话时立即显示，不必等首回合结束）
pub fn session_meta(project_path: &str, session_id: &str) -> SessionMeta {
    let path = session_file(project_path, session_id);
    last_assistant_info(&path.to_string_lossy())
        .map(|l| SessionMeta {
            model: l.model,
            thinking: l.thinking,
        })
        .unwrap_or_default()
}

/// 取某会话最近一条含文本的 assistant 回复（截断 max_chars）。
/// 比 last_assistant_info 的 120 字摘要更长，供微信「查看输出」按需拉取。
pub fn last_assistant_text(project_path: &str, session_id: &str, max_chars: usize) -> String {
    use std::io::{Read, Seek, SeekFrom};
    let path = session_file(project_path, session_id);
    let Ok(mut file) = fs::File::open(&path) else {
        return String::new();
    };
    let Ok(meta) = file.metadata() else {
        return String::new();
    };
    // 多读尾部（512KB），保证较长的末回合回复整段都读得到（微信"完整输出"用）
    let start = meta.len().saturating_sub(512 * 1024);
    if file.seek(SeekFrom::Start(start)).is_err() {
        return String::new();
    }
    let mut bytes = Vec::new();
    if std::io::BufReader::new(file).read_to_end(&mut bytes).is_err() {
        return String::new();
    }
    let buf = String::from_utf8_lossy(&bytes);
    // 倒序找最近一条有文本的 assistant 消息
    for line in buf.split('\n').rev() {
        if line.trim().is_empty() {
            continue;
        }
        let Ok(entry) = serde_json::from_str::<Value>(line) else {
            continue;
        };
        if entry.get("type").and_then(Value::as_str) != Some("assistant") {
            continue;
        }
        if let Some(content) = entry.pointer("/message/content") {
            if let Some(text) = extract_text(content) {
                let t = text.trim();
                if !t.is_empty() {
                    return t.chars().take(max_chars).collect();
                }
            }
        }
    }
    String::new()
}

/// 最近一个待处理的工具调用（AskUserQuestion / 权限确认的 Bash/Edit 等），供微信端把
/// 选项/权限请求渲染成干净的结构化文本。name 为工具名，input 为其原始入参（前端按 name 解析）。
#[derive(Debug, Clone, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct LastToolUse {
    pub name: String,
    pub input: Value,
}

pub fn last_tool_use(project_path: &str, session_id: &str) -> Option<LastToolUse> {
    use std::io::{Read, Seek, SeekFrom};
    let path = session_file(project_path, session_id);
    let mut file = fs::File::open(&path).ok()?;
    let len = file.metadata().ok()?.len();
    let start = len.saturating_sub(256 * 1024);
    file.seek(SeekFrom::Start(start)).ok()?;
    let mut bytes = Vec::new();
    std::io::BufReader::new(file).read_to_end(&mut bytes).ok()?;
    let buf = String::from_utf8_lossy(&bytes);
    // 倒序找最近一条 assistant 消息；它有 tool_use 即待处理的工具，否则是纯文本回复(非工具提示)
    for line in buf.split('\n').rev() {
        if line.trim().is_empty() {
            continue;
        }
        let Ok(entry) = serde_json::from_str::<Value>(line) else {
            continue;
        };
        if entry.get("type").and_then(Value::as_str) != Some("assistant") {
            continue;
        }
        let Some(content) = entry.pointer("/message/content").and_then(Value::as_array) else {
            return None;
        };
        for block in content {
            if block.get("type").and_then(Value::as_str) == Some("tool_use") {
                let name = block
                    .get("name")
                    .and_then(Value::as_str)
                    .unwrap_or("")
                    .to_string();
                if !name.is_empty() {
                    return Some(LastToolUse {
                        name,
                        input: block.get("input").cloned().unwrap_or(Value::Null),
                    });
                }
            }
        }
        return None;
    }
    None
}

/// 把会话导出为 Markdown 文件
pub fn export_session_markdown(
    project_path: &str,
    session_id: &str,
    title: &str,
    dest: &str,
) -> Result<(), String> {
    let messages = preview_session(project_path, session_id)?;
    let mut out = String::new();
    out.push_str(&format!("# {title}\n\n"));
    out.push_str(&format!("- 项目：{project_path}\n"));
    out.push_str(&format!("- 会话：{session_id}\n"));
    out.push_str(&format!(
        "- 导出时间：{}\n\n---\n\n",
        humantime::format_rfc3339_seconds(std::time::SystemTime::now())
    ));
    for msg in messages {
        match msg.role.as_str() {
            "user" => {
                out.push_str("## 用户\n\n");
                out.push_str(&msg.text);
                out.push_str("\n\n");
            }
            "assistant" => {
                out.push_str("## Claude\n\n");
                out.push_str(&msg.text);
                out.push_str("\n\n");
            }
            _ => {
                out.push_str(&format!("> 工具调用：{}\n\n", msg.text));
            }
        }
    }
    fs::write(dest, out).map_err(|e| e.to_string())
}

/// 跨项目搜索会话文本内容（大小写不敏感）
pub fn search_sessions(projects: &[String], query: &str, limit: usize) -> Vec<SearchHit> {
    let needle = query.to_lowercase();
    let mut hits: Vec<SearchHit> = Vec::new();
    if needle.is_empty() {
        return hits;
    }
    'outer: for project in projects {
        for info in list_sessions(project) {
            let path = session_file(project, &info.id);
            let Ok(raw) = fs::read_to_string(&path) else {
                continue;
            };
            for line in raw.split('\n') {
                if line.trim().is_empty() {
                    continue;
                }
                let entry: Value = match serde_json::from_str(line) {
                    Ok(v) => v,
                    Err(_) => continue,
                };
                let role = entry.get("type").and_then(Value::as_str).unwrap_or("");
                if role != "user" && role != "assistant" {
                    continue;
                }
                let Some(content) = entry.pointer("/message/content") else {
                    continue;
                };
                let Some(text) = extract_text(content) else {
                    continue;
                };
                let lower = text.to_lowercase();
                if let Some(pos) = lower.find(&needle) {
                    // 截取命中位置前后各 60 字符作为摘要
                    let start = text
                        .char_indices()
                        .map(|(i, _)| i)
                        .filter(|&i| i <= pos)
                        .rev()
                        .nth(60)
                        .unwrap_or(0);
                    let snippet: String = text[start..].chars().take(160).collect();
                    let collapsed = snippet.split_whitespace().collect::<Vec<_>>().join(" ");
                    hits.push(SearchHit {
                        project_path: project.clone(),
                        session_id: info.id.clone(),
                        title: info.title.clone(),
                        snippet: collapsed,
                        mtime: info.mtime,
                    });
                    break; // 每个会话只取一条命中
                }
            }
            if hits.len() >= limit {
                break 'outer;
            }
        }
    }
    hits.sort_by(|a, b| b.mtime.cmp(&a.mtime));
    hits
}
