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

/// Diviner: infer agent intent from recent file access patterns and surface to ORIENT phase.
pub struct DivinerSkill;

impl Skill for DivinerSkill {
    fn id(&self) -> SkillId {
        SkillId("diviner")
    }
    fn display_name(&self) -> &str {
        "Diviner"
    }

    fn input_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "recently_accessed": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Files accessed in this session, in order"
                },
                "recent_commands": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Recent shell commands run"
                }
            },
            "required": ["recently_accessed"]
        })
    }

    fn output_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "inferred_intent": { "type": "string" },
                "confidence": { "type": "number" },
                "signals": { "type": "array", "items": { "type": "string" } }
            }
        })
    }

    fn execute(&self, input: SkillInput, _ctx: &SkillContext) -> anyhow::Result<SkillOutcome> {
        let files = input.get_str_array("recently_accessed").unwrap_or_default();
        let commands = input.get_str_array("recent_commands").unwrap_or_default();

        let (intent, confidence, signals) = infer_intent(&files, &commands);

        Ok(SkillOutcome::success(
            "diviner",
            json!({
                "inferred_intent": intent,
                "confidence": confidence,
                "signals": signals
            }),
        ))
    }
}

pub(crate) fn infer_intent(files: &[String], commands: &[String]) -> (String, f64, Vec<String>) {
    let mut signals = Vec::new();
    let mut scores: HashMap<&str, f64> = HashMap::new();

    // File-based signals
    for f in files {
        let f_lower = f.to_lowercase();
        if f_lower.contains("test") || f_lower.contains("spec") {
            *scores.entry("testing").or_insert(0.0) += 1.0;
            signals.push(format!("test file: {f}"));
        }
        if f_lower.ends_with(".md") || f_lower.contains("doc") || f_lower.contains("readme") {
            *scores.entry("documentation").or_insert(0.0) += 1.0;
            signals.push(format!("doc file: {f}"));
        }
        if f_lower.contains("migration") || f_lower.contains("schema") || f_lower.ends_with(".sql")
        {
            *scores.entry("database").or_insert(0.0) += 1.5;
            signals.push(format!("db file: {f}"));
        }
        if f_lower.contains("config")
            || f_lower.ends_with(".toml")
            || f_lower.ends_with(".yaml")
            || f_lower.ends_with(".json")
        {
            *scores.entry("configuration").or_insert(0.0) += 0.8;
        }
    }

    // Command-based signals
    for cmd in commands {
        let c = cmd.to_lowercase();
        if c.starts_with("git") {
            if c.contains("diff") || c.contains("log") {
                *scores.entry("code_review").or_insert(0.0) += 1.0;
                signals.push(format!("git review: {cmd}"));
            }
            if c.contains("commit") || c.contains("push") {
                *scores.entry("shipping").or_insert(0.0) += 1.5;
            }
        }
        if c.contains("test") || c.contains("pytest") || c.contains("cargo test") {
            *scores.entry("testing").or_insert(0.0) += 2.0;
            signals.push(format!("test run: {cmd}"));
        }
        if c.contains("build") || c.contains("cargo build") || c.contains("npm run build") {
            *scores.entry("building").or_insert(0.0) += 1.5;
        }
        if c.contains("deploy") || c.contains("kubectl") || c.contains("docker") {
            *scores.entry("deployment").or_insert(0.0) += 2.0;
        }
    }

    let (intent, score) = scores
        .iter()
        .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
        .map(|(k, v)| (*k, *v))
        .unwrap_or(("exploring", 0.0));

    let total: f64 = scores.values().sum();
    let confidence = if total > 0.0 {
        (score / total).min(1.0)
    } else {
        0.3
    };

    (
        intent.to_string(),
        (confidence * 100.0).round() / 100.0,
        signals,
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_input_returns_exploring() {
        let (intent, confidence, _) = infer_intent(&[], &[]);
        assert_eq!(intent, "exploring");
        assert!((confidence - 0.3).abs() < 0.01);
    }

    #[test]
    fn test_files_signal_testing_intent() {
        let files = vec!["src/auth_test.rs".to_string(), "tests/spec.rs".to_string()];
        let (intent, _, _) = infer_intent(&files, &[]);
        assert_eq!(intent, "testing");
    }

    #[test]
    fn git_commit_and_push_signals_shipping() {
        let cmds = vec![
            "git commit -m 'v1.0'".to_string(),
            "git push origin main".to_string(),
        ];
        let (intent, _, _) = infer_intent(&[], &cmds);
        assert_eq!(intent, "shipping");
    }

    #[test]
    fn sql_migration_file_signals_database() {
        let files = vec!["migrations/001_users.sql".to_string()];
        let (intent, _, _) = infer_intent(&files, &[]);
        assert_eq!(intent, "database");
    }

    #[test]
    fn test_command_signals_testing_with_signal() {
        let cmds = vec!["cargo test --workspace".to_string()];
        let (intent, _, signals) = infer_intent(&[], &cmds);
        assert_eq!(intent, "testing");
        assert!(signals.iter().any(|s| s.contains("test run")));
    }

    #[test]
    fn confidence_is_in_zero_to_one_range() {
        let files = vec!["foo_test.rs".to_string(), "bar.rs".to_string()];
        let (_, confidence, _) = infer_intent(&files, &[]);
        assert!(
            confidence >= 0.0 && confidence <= 1.0,
            "confidence={confidence}"
        );
    }

    #[test]
    fn doc_file_signals_documentation() {
        let files = vec!["docs/README.md".to_string(), "CHANGELOG.md".to_string()];
        let (intent, _, _) = infer_intent(&files, &[]);
        assert_eq!(intent, "documentation");
    }
}
