use std::sync::{Arc, Mutex};

use async_trait::async_trait;
use serde_json::json;
use tracing::warn;

use crate::sop::types::{SopEvent, SopRunAction, SopTriggerSource};
use crate::sop::{SopAuditLogger, SopEngine};
use kylinbot_api::tool::{Tool, ToolResult};

/// Manually trigger an SOP by name. Returns the run ID and first step instruction.
pub struct SopExecuteTool {
    engine: Arc<Mutex<SopEngine>>,
    audit: Option<Arc<SopAuditLogger>>,
}

impl SopExecuteTool {
    pub fn new(engine: Arc<Mutex<SopEngine>>) -> Self {
        Self {
            engine,
            audit: None,
        }
    }

    pub fn with_audit(mut self, audit: Arc<SopAuditLogger>) -> Self {
        self.audit = Some(audit);
        self
    }
}

#[async_trait]
impl Tool for SopExecuteTool {
    fn name(&self) -> &str {
        "sop_execute"
    }

    fn description(&self) -> &str {
        "Manually trigger a Standard Operating Procedure (SOP) by name. Returns the run ID and first step instruction. Use sop_list to see available SOPs."
    }

    fn parameters_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "Name of the SOP to execute"
                },
                "payload": {
                    "type": "string",
                    "description": "Optional trigger payload (JSON string)"
                }
            },
            "required": ["name"]
        })
    }

    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
        let sop_name = args
            .get("name")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'name' parameter"))?;

        let payload = args
            .get("payload")
            .and_then(|v| v.as_str())
            .map(String::from);

        let event = SopEvent {
            source: SopTriggerSource::Manual,
            topic: None,
            payload,
            timestamp: now_iso8601(),
        };

        // Lock engine, start run, snapshot run for audit, then drop lock
        let (action, run_snapshot) = {
            let mut engine = self
                .engine
                .lock()
                .map_err(|e| anyhow::anyhow!("Engine lock poisoned: {e}"))?;

            match engine.start_run(sop_name, event) {
                Ok(action) => {
                    let run_id = action_run_id(&action);
                    let snapshot = run_id.and_then(|id| engine.get_run(id).cloned());
                    (Ok(action), snapshot)
                }
                Err(e) => (Err(e), None),
            }
        };

        // Audit log (engine lock dropped, safe to await)
        if let Some(ref audit) = self.audit
            && let Some(ref run) = run_snapshot
            && let Err(e) = audit.log_run_start(run).await
        {
            warn!("SOP audit log_run_start failed: {e}");
        }

        match action {
            Ok(action) => {
                let output = match action {
                    SopRunAction::ExecuteStep {
                        run_id, context, ..
                    } => {
                        format!("SOP run started: {run_id}\n\n{context}")
                    }
                    SopRunAction::WaitApproval {
                        run_id, context, ..
                    } => {
                        format!("SOP run started: {run_id} (waiting for approval)\n\n{context}")
                    }
                    SopRunAction::Completed { run_id, sop_name } => {
                        format!("SOP '{sop_name}' run {run_id} completed immediately (no steps).")
                    }
                    SopRunAction::Failed { run_id, reason, .. } => {
                        format!("SOP run {run_id} failed: {reason}")
                    }
                    SopRunAction::DeterministicStep { run_id, step, .. } => {
                        format!(
                            "SOP run started (deterministic): {run_id}\nFirst step: {}",
                            step.title
                        )
                    }
                    SopRunAction::CheckpointWait { run_id, step, .. } => {
                        format!(
                            "SOP run started: {run_id} (paused at checkpoint: {})",
                            step.title
                        )
                    }
                };
                Ok(ToolResult {
                    success: true,
                    output,
                    error: None,
                })
            }
            Err(e) => Ok(ToolResult {
                success: false,
                output: String::new(),
                error: Some(format!("Failed to start SOP: {e}")),
            }),
        }
    }
}

/// Extract run_id from any SopRunAction variant.
fn action_run_id(action: &SopRunAction) -> Option<&str> {
    match action {
        SopRunAction::ExecuteStep { run_id, .. }
        | SopRunAction::WaitApproval { run_id, .. }
        | SopRunAction::Completed { run_id, .. }
        | SopRunAction::Failed { run_id, .. }
        | SopRunAction::DeterministicStep { run_id, .. }
        | SopRunAction::CheckpointWait { run_id, .. } => Some(run_id),
    }
}

use crate::sop::engine::now_iso8601;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sop::engine::SopEngine;
    use crate::sop::types::*;
    use kylinbot_config::schema::SopConfig;

    fn test_sop(name: &str, mode: SopExecutionMode) -> Sop {
        Sop {
            name: name.into(),
            description: format!("Test SOP: {name}"),
            version: "1.0.0".into(),
            priority: SopPriority::Normal,
            execution_mode: mode,
            triggers: vec![SopTrigger::Manual],
            steps: vec![
                SopStep {
                    number: 1,
                    title: "Step one".into(),
                    body: "Do step one".into(),
                    suggested_tools: vec!["shell".into()],
                    requires_confirmation: false,
                    kind: SopStepKind::default(),
                    schema: None,
                },
                SopStep {
                    number: 2,
                    title: "Step two".into(),
                    body: "Do step two".into(),
                    suggested_tools: vec![],
                    requires_confirmation: false,
                    kind: SopStepKind::default(),
                    schema: None,
                },
            ],
            cooldown_secs: 0,
            max_concurrent: 1,
            location: None,
            deterministic: false,
        }
    }

    fn engine_with_sops(sops: Vec<Sop>) -> Arc<Mutex<SopEngine>> {
        let mut engine = SopEngine::new(SopConfig::default());
        engine.set_sops_for_test(sops);
        Arc::new(Mutex::new(engine))
    }

    #[tokio::test]
    async fn execute_auto_sop() {
        let engine = engine_with_sops(vec![test_sop("test-sop", SopExecutionMode::Auto)]);
        let tool = SopExecuteTool::new(engine);
        let result = tool.execute(json!({"name": "test-sop"})).await.unwrap();
        assert!(result.success);
        assert!(result.output.contains("run-"));
        assert!(result.output.contains("Step one"));
    }

    #[tokio::test]
    async fn execute_supervised_sop() {
        let engine = engine_with_sops(vec![test_sop("test-sop", SopExecutionMode::Supervised)]);
        let tool = SopExecuteTool::new(engine);
        let result = tool.execute(json!({"name": "test-sop"})).await.unwrap();
        assert!(result.success);
        assert!(result.output.contains("waiting for approval"));
    }

    #[tokio::test]
    async fn execute_unknown_sop() {
        let engine = engine_with_sops(vec![]);
        let tool = SopExecuteTool::new(engine);
        let result = tool.execute(json!({"name": "nonexistent"})).await.unwrap();
        assert!(!result.success);
        assert!(result.error.unwrap().contains("Failed to start SOP"));
    }

    #[tokio::test]
    async fn execute_missing_name() {
        let engine = engine_with_sops(vec![]);
        let tool = SopExecuteTool::new(engine);
        let result = tool.execute(json!({})).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn execute_with_payload() {
        let engine = engine_with_sops(vec![test_sop("test-sop", SopExecutionMode::Auto)]);
        let tool = SopExecuteTool::new(engine);
        let result = tool
            .execute(json!({"name": "test-sop", "payload": "{\"value\": 87.3}"}))
            .await
            .unwrap();
        assert!(result.success);
        assert!(result.output.contains("87.3"));
    }

    #[test]
    fn name_and_schema() {
        let engine = engine_with_sops(vec![]);
        let tool = SopExecuteTool::new(engine);
        assert_eq!(tool.name(), "sop_execute");
        assert!(tool.parameters_schema()["required"].is_array());
    }
}
