use rusqlite::{params, Connection};
use serde::Serialize;
use std::collections::HashSet;

/// 符号命中（定义或定位结果）
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SymbolHit {
    pub name: String,
    pub kind: String,
    pub file: String,
    pub line: u32,
    /// 定义结束行（含），供取源码片段用
    pub end_line: u32,
    pub signature: String,
}

/// 引用命中
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RefHit {
    pub file: String,
    pub line: u32,
    pub kind: String,
    pub signature: String,
    /// 置信度：high=引用所在文件确实导入了定义所在文件；low=仅名字匹配
    pub confidence: String,
}

/// 反向依赖命中：谁导入了目标文件
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ImporterHit {
    pub file: String,
    /// 该 import 使用的原始 specifier
    pub module: String,
}

/// 全文检索命中（按行）
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TextHit {
    pub file: String,
    pub line: u32,
    pub content: String,
}

/// 解析问题条目
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ParseIssueRow {
    pub file: String,
    pub lang: String,
    pub reason: String,
}

/// 被调用项（某函数体内调用到的符号）
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CalleeHit {
    pub name: String,
    pub kind: String,
    pub line: u32,
}

/// 一条继承关系
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TypeRelation {
    pub file: String,
    pub child: String,
    pub parent: String,
}

/// 类型层级：supertypes=该类型继承/实现了谁；subtypes=谁继承/实现了该类型
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TypeHierarchy {
    pub supertypes: Vec<TypeRelation>,
    pub subtypes: Vec<TypeRelation>,
}

fn type_relations(conn: &Connection, column: &str, name: &str) -> Result<Vec<TypeRelation>, String> {
    // column 仅取自固定字面量 "child"/"parent"，无注入风险
    let sql = format!(
        "SELECT f.rel_path, i.child, i.parent FROM inherits i \
         JOIN files f ON f.id = i.file_id WHERE i.{column} = ?1 \
         ORDER BY f.rel_path LIMIT 500"
    );
    let mut stmt = conn.prepare(&sql).map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map(params![name], |r| {
            Ok(TypeRelation {
                file: r.get(0)?,
                child: r.get(1)?,
                parent: r.get(2)?,
            })
        })
        .map_err(|e| e.to_string())?;
    let mut out = Vec::new();
    for r in rows {
        out.push(r.map_err(|e| e.to_string())?);
    }
    Ok(out)
}

/// 类型层级：父类型（name 作为 child 时的 parent）+ 子类型（name 作为 parent 时的 child）
pub fn type_hierarchy(conn: &Connection, name: &str) -> Result<TypeHierarchy, String> {
    Ok(TypeHierarchy {
        supertypes: type_relations(conn, "child", name)?,
        subtypes: type_relations(conn, "parent", name)?,
    })
}

/// 调用层级：谁调用了它（callers）+ 它调用了谁（callees）
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CallHierarchy {
    pub callers: Vec<RefHit>,
    pub callees: Vec<CalleeHit>,
}

/// 调用层级：callers 复用带置信度的引用查找；callees 取定义体行号范围内的引用（同文件）
pub fn call_hierarchy(conn: &Connection, name: &str) -> Result<CallHierarchy, String> {
    let callers = find_references(conn, name, 200)?;
    // 定义的 (file_id, start, end)
    let mut defs: Vec<(i64, u32, u32)> = Vec::new();
    {
        let mut stmt = conn
            .prepare(
                "SELECT file_id, start_line, end_line FROM symbols WHERE name = ?1 AND is_def = 1",
            )
            .map_err(|e| e.to_string())?;
        let rows = stmt
            .query_map(params![name], |r| {
                Ok((
                    r.get::<_, i64>(0)?,
                    r.get::<_, i64>(1)? as u32,
                    r.get::<_, i64>(2)? as u32,
                ))
            })
            .map_err(|e| e.to_string())?;
        for r in rows {
            defs.push(r.map_err(|e| e.to_string())?);
        }
    }
    let mut callees = Vec::new();
    let mut seen: HashSet<String> = HashSet::new();
    let mut stmt = conn
        .prepare(
            "SELECT name, kind, start_line FROM symbols \
             WHERE is_def = 0 AND file_id = ?1 AND start_line BETWEEN ?2 AND ?3 AND name != ?4 \
             ORDER BY start_line",
        )
        .map_err(|e| e.to_string())?;
    for (fid, start, end) in &defs {
        let rows = stmt
            .query_map(params![fid, *start as i64, *end as i64, name], |r| {
                Ok(CalleeHit {
                    name: r.get(0)?,
                    kind: r.get(1)?,
                    line: r.get::<_, i64>(2)? as u32,
                })
            })
            .map_err(|e| e.to_string())?;
        for r in rows {
            let hit = r.map_err(|e| e.to_string())?;
            if seen.insert(hit.name.clone()) {
                callees.push(hit);
            }
        }
    }
    Ok(CallHierarchy { callers, callees })
}

/// 索引状态摘要（供 MCP index_status 暴露）
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IndexSummary {
    pub files: u64,
    pub symbols: u64,
    pub last_scan_ms: u64,
    pub parse_issue_count: u64,
    /// 解析失败文件样本（最多 100 条）
    pub parse_issues: Vec<ParseIssueRow>,
}

/// 索引状态：文件/符号数、上次扫描时间、解析问题清单（发现索引盲区）
pub fn index_summary(conn: &Connection) -> Result<IndexSummary, String> {
    let (files, symbols) = super::db::counts(conn);
    let last_scan_ms: u64 = conn
        .query_row("SELECT value FROM meta WHERE key = 'last_scan_ms'", [], |r| {
            r.get::<_, String>(0)
        })
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(0);
    let parse_issue_count: i64 = conn
        .query_row("SELECT COUNT(*) FROM parse_issues", [], |r| r.get(0))
        .unwrap_or(0);
    let mut parse_issues = Vec::new();
    {
        let mut stmt = conn
            .prepare("SELECT rel_path, lang, reason FROM parse_issues ORDER BY rel_path LIMIT 100")
            .map_err(|e| e.to_string())?;
        let rows = stmt
            .query_map([], |r| {
                Ok(ParseIssueRow {
                    file: r.get(0)?,
                    lang: r.get(1)?,
                    reason: r.get(2)?,
                })
            })
            .map_err(|e| e.to_string())?;
        for r in rows {
            parse_issues.push(r.map_err(|e| e.to_string())?);
        }
    }
    Ok(IndexSummary {
        files,
        symbols,
        last_scan_ms,
        parse_issue_count: parse_issue_count as u64,
        parse_issues,
    })
}

/// 某文件的导入
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportInfo {
    pub file: String,
    pub modules: Vec<String>,
}

/// 转义 LIKE 的特殊字符（配合 ESCAPE '\'）
fn escape_like(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for c in s.chars() {
        if c == '\\' || c == '%' || c == '_' {
            out.push('\\');
        }
        out.push(c);
    }
    out
}

fn map_symbol(row: &rusqlite::Row) -> rusqlite::Result<SymbolHit> {
    Ok(SymbolHit {
        file: row.get(0)?,
        name: row.get(1)?,
        kind: row.get(2)?,
        line: row.get::<_, i64>(3)? as u32,
        end_line: row.get::<_, i64>(4)? as u32,
        signature: row.get(5)?,
    })
}

/// 模糊搜索符号定义：精确 > 前缀 > 子串排序，可按 kind 过滤
pub fn search_symbols(
    conn: &Connection,
    query: &str,
    kind: Option<&str>,
    limit: u32,
) -> Result<Vec<SymbolHit>, String> {
    let esc = escape_like(query);
    let pattern = format!("%{esc}%");
    let prefix = format!("{esc}%");
    let lim = limit.clamp(1, 500);
    let mut sql = String::from(
        "SELECT f.rel_path, s.name, s.kind, s.start_line, s.end_line, s.signature \
         FROM symbols s JOIN files f ON f.id = s.file_id \
         WHERE s.is_def = 1 AND s.name LIKE ?1 ESCAPE '\\'",
    );
    if kind.is_some() {
        sql.push_str(" AND s.kind = ?4");
    }
    sql.push_str(&format!(
        " ORDER BY CASE WHEN s.name = ?2 THEN 0 WHEN s.name LIKE ?3 ESCAPE '\\' THEN 1 ELSE 2 END, \
          length(s.name), s.name LIMIT {lim}"
    ));
    let mut stmt = conn.prepare(&sql).map_err(|e| e.to_string())?;
    let mut out = Vec::new();
    if let Some(k) = kind {
        let rows = stmt
            .query_map(params![pattern, query, prefix, k], map_symbol)
            .map_err(|e| e.to_string())?;
        for r in rows {
            out.push(r.map_err(|e| e.to_string())?);
        }
    } else {
        let rows = stmt
            .query_map(params![pattern, query, prefix], map_symbol)
            .map_err(|e| e.to_string())?;
        for r in rows {
            out.push(r.map_err(|e| e.to_string())?);
        }
    }
    Ok(out)
}

/// 精确按名跳转到定义
pub fn goto_definition(
    conn: &Connection,
    name: &str,
    kind: Option<&str>,
) -> Result<Vec<SymbolHit>, String> {
    let mut sql = String::from(
        "SELECT f.rel_path, s.name, s.kind, s.start_line, s.end_line, s.signature \
         FROM symbols s JOIN files f ON f.id = s.file_id \
         WHERE s.is_def = 1 AND s.name = ?1",
    );
    if kind.is_some() {
        sql.push_str(" AND s.kind = ?2");
    }
    sql.push_str(" ORDER BY f.rel_path, s.start_line LIMIT 100");
    let mut stmt = conn.prepare(&sql).map_err(|e| e.to_string())?;
    let mut out = Vec::new();
    if let Some(k) = kind {
        let rows = stmt
            .query_map(params![name, k], map_symbol)
            .map_err(|e| e.to_string())?;
        for r in rows {
            out.push(r.map_err(|e| e.to_string())?);
        }
    } else {
        let rows = stmt
            .query_map(params![name], map_symbol)
            .map_err(|e| e.to_string())?;
        for r in rows {
            out.push(r.map_err(|e| e.to_string())?);
        }
    }
    Ok(out)
}

/// 文件大纲：列出该文件内的所有定义。rel 用正斜杠相对路径，匹配精确或后缀
pub fn file_outline(conn: &Connection, rel: &str) -> Result<Vec<SymbolHit>, String> {
    let rel = rel.trim_start_matches("./").replace('\\', "/");
    let suffix = format!("%/{rel}");
    let mut stmt = conn
        .prepare(
            "SELECT f.rel_path, s.name, s.kind, s.start_line, s.end_line, s.signature \
             FROM symbols s JOIN files f ON f.id = s.file_id \
             WHERE s.is_def = 1 AND (f.rel_path = ?1 OR f.rel_path LIKE ?2) \
             ORDER BY s.start_line LIMIT 1000",
        )
        .map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map(params![rel, suffix], map_symbol)
        .map_err(|e| e.to_string())?;
    let mut out = Vec::new();
    for r in rows {
        out.push(r.map_err(|e| e.to_string())?);
    }
    Ok(out)
}

/// 查找引用（非定义出现处），附置信度：当引用所在文件确实导入了该符号定义所在文件时
/// 标 high（高置信优先排序），否则 low（保留"基于语法标记、非语义解析"的局限）。
pub fn find_references(conn: &Connection, name: &str, limit: u32) -> Result<Vec<RefHit>, String> {
    // 1. 定义所在文件 id 集合
    let mut def_files: HashSet<i64> = HashSet::new();
    {
        let mut stmt = conn
            .prepare("SELECT DISTINCT file_id FROM symbols WHERE name = ?1 AND is_def = 1")
            .map_err(|e| e.to_string())?;
        let rows = stmt
            .query_map(params![name], |r| r.get::<_, i64>(0))
            .map_err(|e| e.to_string())?;
        for r in rows {
            def_files.insert(r.map_err(|e| e.to_string())?);
        }
    }
    // 2. 导入了任一定义文件的文件 id 集合（这些文件里的同名引用为高置信）
    let mut importers: HashSet<i64> = HashSet::new();
    if !def_files.is_empty() {
        // def_files 是本库自产整数 id，内联进 IN 无注入风险
        let ids = def_files
            .iter()
            .map(|i| i.to_string())
            .collect::<Vec<_>>()
            .join(",");
        let mut stmt = conn
            .prepare(&format!(
                "SELECT DISTINCT file_id FROM imports WHERE resolved_file_id IN ({ids})"
            ))
            .map_err(|e| e.to_string())?;
        let rows = stmt
            .query_map([], |r| r.get::<_, i64>(0))
            .map_err(|e| e.to_string())?;
        for r in rows {
            importers.insert(r.map_err(|e| e.to_string())?);
        }
    }
    // 3. 取所有引用并按置信度标注
    let mut out: Vec<RefHit> = Vec::new();
    {
        let mut stmt = conn
            .prepare(
                "SELECT f.rel_path, s.start_line, s.kind, s.signature, s.file_id \
                 FROM symbols s JOIN files f ON f.id = s.file_id \
                 WHERE s.is_def = 0 AND s.name = ?1",
            )
            .map_err(|e| e.to_string())?;
        let rows = stmt
            .query_map(params![name], |row| {
                let file_id: i64 = row.get(4)?;
                Ok((
                    RefHit {
                        file: row.get(0)?,
                        line: row.get::<_, i64>(1)? as u32,
                        kind: row.get(2)?,
                        signature: row.get(3)?,
                        confidence: String::new(),
                    },
                    file_id,
                ))
            })
            .map_err(|e| e.to_string())?;
        for r in rows {
            let (mut hit, file_id) = r.map_err(|e| e.to_string())?;
            hit.confidence = if importers.contains(&file_id) {
                "high".into()
            } else {
                "low".into()
            };
            out.push(hit);
        }
    }
    // 高置信优先，其次按文件/行
    out.sort_by(|a, b| {
        let rank = |c: &str| if c == "high" { 0 } else { 1 };
        rank(&a.confidence)
            .cmp(&rank(&b.confidence))
            .then_with(|| a.file.cmp(&b.file))
            .then_with(|| a.line.cmp(&b.line))
    });
    out.truncate(limit.clamp(1, 500) as usize);
    Ok(out)
}

/// 全文检索（FTS5 trigram）：搜源码行内容，能命中字符串字面量/注释/TODO/配置 key 等符号表覆盖不到的内容。
pub fn search_text(conn: &Connection, query: &str, limit: u32) -> Result<Vec<TextHit>, String> {
    let q = query.trim();
    if q.len() < 3 {
        return Err("全文检索关键词至少 3 个字符（trigram 限制）".into());
    }
    // 包成带引号的字符串字面量，内部双引号转义，避免 FTS5 查询语法歧义
    let match_expr = format!("\"{}\"", q.replace('"', "\"\""));
    let lim = limit.clamp(1, 200);
    let mut stmt = conn
        .prepare("SELECT file, line, content FROM fts WHERE fts MATCH ?1 LIMIT ?2")
        .map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map(params![match_expr, lim], |r| {
            Ok(TextHit {
                file: r.get(0)?,
                line: r.get::<_, i64>(1)? as u32,
                content: r.get::<_, String>(2)?.trim().to_string(),
            })
        })
        .map_err(|e| e.to_string())?;
    let mut out = Vec::new();
    for r in rows {
        out.push(r.map_err(|e| e.to_string())?);
    }
    Ok(out)
}

/// 反向依赖：哪些文件 import 了目标文件（rel 支持精确或后缀匹配）。依赖已解析的 resolved_file_id。
pub fn who_imports(conn: &Connection, rel: &str) -> Result<Vec<ImporterHit>, String> {
    let rel = rel.trim_start_matches("./").replace('\\', "/");
    let suffix = format!("%/{rel}");
    let mut stmt = conn
        .prepare(
            "SELECT DISTINCT f.rel_path, i.module \
             FROM imports i \
             JOIN files f ON f.id = i.file_id \
             JOIN files t ON t.id = i.resolved_file_id \
             WHERE t.rel_path = ?1 OR t.rel_path LIKE ?2 \
             ORDER BY f.rel_path",
        )
        .map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map(params![rel, suffix], |r| {
            Ok(ImporterHit {
                file: r.get(0)?,
                module: r.get(1)?,
            })
        })
        .map_err(|e| e.to_string())?;
    let mut out = Vec::new();
    for r in rows {
        out.push(r.map_err(|e| e.to_string())?);
    }
    Ok(out)
}

/// 模块依赖：指定文件则只看该文件，否则列出所有有导入的文件
pub fn module_graph(conn: &Connection, rel: Option<&str>) -> Result<Vec<ImportInfo>, String> {
    let map_pair =
        |r: &rusqlite::Row| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?));
    // 各分支各自收集，避免两个不同闭包类型共享同一绑定
    let pairs: Vec<(String, String)> = if let Some(rel) = rel {
        let rel = rel.trim_start_matches("./").replace('\\', "/");
        let suffix = format!("%/{rel}");
        let mut stmt = conn
            .prepare(
                "SELECT f.rel_path, i.module FROM imports i JOIN files f ON f.id = i.file_id \
                 WHERE f.rel_path = ?1 OR f.rel_path LIKE ?2 ORDER BY f.rel_path, i.module",
            )
            .map_err(|e| e.to_string())?;
        let rows = stmt
            .query_map(params![rel, suffix], map_pair)
            .map_err(|e| e.to_string())?;
        rows.collect::<Result<_, _>>().map_err(|e| e.to_string())?
    } else {
        let mut stmt = conn
            .prepare(
                "SELECT f.rel_path, i.module FROM imports i JOIN files f ON f.id = i.file_id \
                 ORDER BY f.rel_path, i.module LIMIT 5000",
            )
            .map_err(|e| e.to_string())?;
        let rows = stmt
            .query_map([], map_pair)
            .map_err(|e| e.to_string())?;
        rows.collect::<Result<_, _>>().map_err(|e| e.to_string())?
    };
    // 按文件聚合
    let mut acc: Vec<ImportInfo> = Vec::new();
    for (file, module) in pairs {
        match acc.last_mut() {
            Some(last) if last.file == file => last.modules.push(module),
            _ => acc.push(ImportInfo {
                file,
                modules: vec![module],
            }),
        }
    }
    Ok(acc)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::index::db;
    use crate::index::parse::{ParsedFile, ParsedSymbol};

    #[test]
    fn summary_counts_symbols_meta_and_issues() {
        let path = std::env::temp_dir().join(format!("ccgui_q_{}.db", std::process::id()));
        let _ = std::fs::remove_file(&path);
        let conn = db::open_rw(&path).unwrap();
        let pf = ParsedFile {
            lang: "rust".into(),
            symbols: vec![ParsedSymbol {
                name: "f".into(),
                kind: "function".into(),
                is_definition: true,
                start_line: 1,
                end_line: 1,
                signature: "fn f()".into(),
            }],
            imports: vec![],
            inherits: vec![],
        };
        db::upsert_file(&conn, "a.rs", 1, &pf, "fn f() {}\n").unwrap();
        db::record_parse_issue(&conn, "big.ts", "ts", "too_large").unwrap();
        db::set_meta(&conn, "last_scan_ms", "12345").unwrap();

        let s = index_summary(&conn).unwrap();
        assert_eq!(s.files, 1);
        assert_eq!(s.symbols, 1);
        assert_eq!(s.last_scan_ms, 12345);
        assert_eq!(s.parse_issue_count, 1);
        assert_eq!(s.parse_issues[0].file, "big.ts");
        assert_eq!(s.parse_issues[0].reason, "too_large");

        // 再次成功 upsert 同名问题文件应清除其问题记录
        db::upsert_file(&conn, "big.ts", 1, &pf, "x\n").unwrap();
        assert_eq!(index_summary(&conn).unwrap().parse_issue_count, 0);

        drop(conn);
        let _ = std::fs::remove_file(&path);
        let _ = std::fs::remove_file(path.with_extension("db-wal"));
        let _ = std::fs::remove_file(path.with_extension("db-shm"));
    }
}
