use crate::{
    invocation::{SkillContext, SkillInput},
    outcome::SkillOutcome,
    skill::{Skill, SkillId},
};
use serde_json::json;

/// Witness: record and replay tool-call sequences for testing and debugging.
pub struct WitnessSkill;

impl Skill for WitnessSkill {
    fn id(&self) -> SkillId {
        SkillId("witness")
    }
    fn display_name(&self) -> &str {
        "Witness"
    }

    fn input_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["record", "list", "replay", "clear"],
                    "description": "record: start recording · list: show recordings · replay: replay a recording · clear: delete recording"
                },
                "recording_id": { "type": "string", "description": "Recording ID (for replay/clear)" },
                "event": {
                    "type": "object",
                    "description": "Tool call event to record (for action=record)",
                    "properties": {
                        "skill": { "type": "string" },
                        "input": { "type": "object" },
                        "output": { "type": "object" }
                    }
                }
            },
            "required": ["action"]
        })
    }

    fn output_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "recording_id": { "type": "string" },
                "recordings": { "type": "array" },
                "events": { "type": "array" },
                "success": { "type": "boolean" }
            }
        })
    }

    fn execute(&self, input: SkillInput, _ctx: &SkillContext) -> anyhow::Result<SkillOutcome> {
        let action = input.get_str("action").unwrap_or("list");
        let witness_dir = witness_dir();

        match action {
            "record" => {
                let event = input.get("event").cloned().unwrap_or(json!({}));
                let rec_id = format!(
                    "{:x}",
                    std::time::SystemTime::now()
                        .duration_since(std::time::UNIX_EPOCH)
                        .unwrap_or_default()
                        .as_millis()
                );
                let path = witness_dir.join(format!("{rec_id}.json"));
                let _ = std::fs::create_dir_all(&witness_dir);
                let success = std::fs::write(
                    &path,
                    serde_json::to_string_pretty(&json!({
                        "id": rec_id,
                        "recorded_at": chrono::Utc::now().to_rfc3339(),
                        "event": event
                    }))?,
                )
                .is_ok();
                Ok(SkillOutcome::success(
                    "witness",
                    json!({ "recording_id": rec_id, "success": success }),
                ))
            }
            "list" => {
                let recordings = list_recordings(&witness_dir);
                Ok(SkillOutcome::success(
                    "witness",
                    json!({ "recordings": recordings }),
                ))
            }
            "replay" => {
                let rec_id = input.get_str("recording_id").unwrap_or("");
                let path = witness_dir.join(format!("{rec_id}.json"));
                match std::fs::read_to_string(&path) {
                    Ok(s) => {
                        let data: serde_json::Value = serde_json::from_str(&s).unwrap_or(json!({}));
                        Ok(SkillOutcome::success(
                            "witness",
                            json!({ "recording_id": rec_id, "events": [data], "success": true }),
                        ))
                    }
                    Err(_) => Ok(SkillOutcome::failure(
                        "witness",
                        &format!("recording '{rec_id}' not found"),
                    )),
                }
            }
            "clear" => {
                let rec_id = input.get_str("recording_id").unwrap_or("");
                let path = witness_dir.join(format!("{rec_id}.json"));
                let success = std::fs::remove_file(&path).is_ok();
                Ok(SkillOutcome::success(
                    "witness",
                    json!({ "recording_id": rec_id, "success": success }),
                ))
            }
            _ => Ok(SkillOutcome::failure("witness", "unknown action")),
        }
    }
}

fn witness_dir() -> std::path::PathBuf {
    std::env::var("HOME")
        .map(|h| std::path::PathBuf::from(h).join(".bctx").join("witness"))
        .unwrap_or_else(|_| std::path::PathBuf::from("/tmp/bctx-witness"))
}

fn list_recordings(dir: &std::path::Path) -> Vec<serde_json::Value> {
    let Ok(rd) = std::fs::read_dir(dir) else {
        return vec![];
    };
    let mut recs: Vec<serde_json::Value> = rd
        .flatten()
        .filter(|e| {
            e.path()
                .extension()
                .and_then(|x| x.to_str())
                .map(|x| x == "json")
                .unwrap_or(false)
        })
        .filter_map(|e| {
            let id = e.path().file_stem()?.to_str()?.to_string();
            let meta = e.metadata().ok()?;
            let modified = meta.modified().ok()?;
            let ts = modified
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs();
            Some(json!({ "id": id, "recorded_at_unix": ts }))
        })
        .collect();
    recs.sort_by(|a, b| {
        b["recorded_at_unix"]
            .as_u64()
            .cmp(&a["recorded_at_unix"].as_u64())
    });
    recs
}
