use crate::{
    invocation::{SkillContext, SkillInput},
    outcome::SkillOutcome,
    skill::{Skill, SkillId},
    vault_store,
};
use forge::tracker::store::ExecutionStore;
use serde_json::json;
use vault::retrieval::query::VaultQuery;

pub struct ChroniclerSkill;

impl Skill for ChroniclerSkill {
    fn id(&self) -> SkillId {
        SkillId("chronicler")
    }
    fn display_name(&self) -> &str {
        "Chronicler"
    }

    fn input_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "session_id": { "type": "string" },
                "scope": { "type": "string", "enum": ["full", "diff", "summary"], "default": "summary" },
                "format": { "type": "string", "enum": ["markdown", "json", "plain"], "default": "markdown" },
                "limit": { "type": "integer", "default": 20 }
            }
        })
    }

    fn output_schema(&self) -> serde_json::Value {
        json!({ "type": "object", "properties": {
            "narrative": { "type": "string" },
            "outcome": { "type": "string" },
            "events_summarized": { "type": "integer" },
            "duration_secs": { "type": "number" },
            "key_changes": { "type": "array" }
        }})
    }

    fn execute(&self, input: SkillInput, ctx: &SkillContext) -> anyhow::Result<SkillOutcome> {
        let session = input
            .get_str("session_id")
            .unwrap_or(&ctx.session_id)
            .to_string();
        let format = input.get_str("format").unwrap_or("markdown");
        let limit = input.get_u64("limit").unwrap_or(20) as usize;

        // Gather recent execution events
        let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
        let db_path = std::path::PathBuf::from(&home)
            .join(".bctx")
            .join("executions.db");
        let exec_events = ExecutionStore::open(&db_path)
            .and_then(|s| s.recent(limit))
            .unwrap_or_default();

        // Gather vault knowledge from this session
        let vault_hits = {
            let vault = vault_store::vault().lock().unwrap();
            let q = VaultQuery::new("").top_k(limit);
            vault.query(&q)
        };

        // Key changes: vault facts stored in this session
        let key_changes: Vec<String> = vault_hits
            .iter()
            .filter(|f| f.source_session.as_deref() == Some(&session))
            .map(|f| format!("[{}] {} = {}", f.category, f.key, f.value))
            .collect();

        let events_count = exec_events.len() + vault_hits.len();

        // Build narrative
        let narrative = match format {
            "json" => {
                let execs: Vec<serde_json::Value> = exec_events.iter().map(|e| json!({
                    "command": format!("{} {}", e.program, e.args),
                    "exit_code": e.exit_code,
                    "savings_pct": if e.tokens_before > 0 { (1.0 - e.tokens_after as f64 / e.tokens_before as f64) * 100.0 } else { 0.0 }
                })).collect();
                json!({ "session": session, "executions": execs, "vault_facts": key_changes.len() })
                    .to_string()
            }
            "plain" => {
                let mut s = format!("Session: {session}\n");
                for e in &exec_events {
                    s.push_str(&format!(
                        "  {} {} [exit={}]\n",
                        e.program, e.args, e.exit_code
                    ));
                }
                if !key_changes.is_empty() {
                    s.push_str("\nKey changes:\n");
                    for kc in &key_changes {
                        s.push_str(&format!("  {kc}\n"));
                    }
                }
                s
            }
            _ => {
                // Markdown
                let mut md = format!("## Session Narrative: `{session}`\n\n");

                if exec_events.is_empty() {
                    md.push_str("_No commands executed yet._\n\n");
                } else {
                    md.push_str("### Commands\n\n");
                    for e in &exec_events {
                        let savings = if e.tokens_before > 0 {
                            format!(
                                " (−{:.0}% tokens)",
                                (1.0 - e.tokens_after as f64 / e.tokens_before as f64) * 100.0
                            )
                        } else {
                            String::new()
                        };
                        md.push_str(&format!(
                            "- `{} {}`  exit={}{}\n",
                            e.program, e.args, e.exit_code, savings
                        ));
                    }
                    md.push('\n');
                }

                if !key_changes.is_empty() {
                    md.push_str("### Key Changes\n\n");
                    for kc in &key_changes {
                        md.push_str(&format!("- {kc}\n"));
                    }
                    md.push('\n');
                }

                let total_before: u64 = exec_events.iter().map(|e| e.tokens_before as u64).sum();
                let total_after: u64 = exec_events.iter().map(|e| e.tokens_after as u64).sum();
                if total_before > 0 {
                    let pct = (1.0 - total_after as f64 / total_before as f64) * 100.0;
                    md.push_str(&format!(
                        "### Token Savings\n\n{total_before} → {total_after} ({pct:.1}% saved)\n"
                    ));
                }

                md
            }
        };

        Ok(SkillOutcome::success(
            "chronicler",
            json!({
                "narrative": narrative,
                "outcome": if exec_events.is_empty() { "empty" } else { "ok" },
                "events_summarized": events_count,
                "duration_secs": 0.0,
                "key_changes": key_changes
            }),
        ))
    }
}
