use std::sync::Arc;

use async_trait::async_trait;
use serde_json::{Value, json};

use crate::agent::{AgentCoordinator, ExecutionMode, SubAgentConfig, SubAgentManager, TaskStatus};
use crate::tools::traits::{Tool, ToolExecutionContext, ToolOutput, ToolResult};

pub struct DelegateTool {
    sub_agent_manager: Arc<SubAgentManager>,
    coordinator: Option<Arc<AgentCoordinator>>,
}

/// Per-run schema view for a child Agent. Authorization still happens in the
/// manager from ToolExecutionContext; this wrapper keeps the model-visible
/// target enum aligned with that Agent's configured outgoing edges.
pub(crate) struct ScopedDelegateTool {
    inner: Arc<dyn Tool>,
    targets: Vec<String>,
}

impl ScopedDelegateTool {
    pub(crate) fn new(inner: Arc<dyn Tool>, targets: Vec<String>) -> Self {
        Self { inner, targets }
    }
}

#[async_trait]
impl Tool for ScopedDelegateTool {
    fn name(&self) -> &str {
        self.inner.name()
    }

    fn description(&self) -> &str {
        self.inner.description()
    }

    fn parameters_schema(&self) -> Value {
        let mut schema = self.inner.parameters_schema();
        schema["properties"]["target"]["enum"] = json!(self.targets);
        schema["properties"]["tasks"]["items"]["properties"]["target"]["enum"] =
            json!(self.targets);
        schema
    }

    fn read_only(&self) -> bool {
        self.inner.read_only()
    }

    fn runtime_injected(&self) -> bool {
        true
    }

    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
        self.inner.execute(args).await
    }

    async fn execute_with_context(
        &self,
        context: &ToolExecutionContext,
        args: Value,
    ) -> anyhow::Result<ToolOutput> {
        self.inner.execute_with_context(context, args).await
    }
}

impl DelegateTool {
    pub fn new(sub_agent_manager: Arc<SubAgentManager>) -> Self {
        Self {
            sub_agent_manager,
            coordinator: None,
        }
    }

    pub fn with_coordinator(mut self, coordinator: Arc<AgentCoordinator>) -> Self {
        self.coordinator = Some(coordinator);
        self
    }

    fn task_schema(&self) -> Value {
        let targets: Vec<_> = self
            .sub_agent_manager
            .catalog()
            .root_targets()
            .into_iter()
            .map(|definition| definition.id.clone())
            .collect();
        let mut target = json!({
            "type": "string",
            "description": "目标具名 Agent ID（来自 agent_orchestration 的 agents 目录）"
        });
        if !targets.is_empty() {
            target["enum"] = json!(targets);
        }
        json!({
            "type": "object",
            "properties": {
                "target": target,
                "task": { "type": "string", "description": "明确、独立、可验收的子任务" },
                "context": { "type": "string", "description": "完成任务所需的显式上下文；不会继承主会话历史" },
                "plan_item_id": { "type": "string", "description": "可选的当前计划子项 ID" }
            },
            "required": ["task"]
        })
    }

    fn parse_config(&self, value: &Value, mode: ExecutionMode) -> anyhow::Result<SubAgentConfig> {
        let prompt = value
            .get("task")
            .or_else(|| value.get("prompt"))
            .and_then(Value::as_str)
            .ok_or_else(|| anyhow::anyhow!("missing required parameter: task"))?
            .trim()
            .to_string();
        if prompt.is_empty() {
            anyhow::bail!("task must not be empty");
        }
        let allowed_tools = value
            .get("allowed_tools")
            .and_then(Value::as_array)
            .map(|items| {
                items
                    .iter()
                    .filter_map(Value::as_str)
                    .map(str::to_string)
                    .collect()
            });
        Ok(SubAgentConfig {
            target: value
                .get("target")
                .and_then(Value::as_str)
                .map(str::to_string),
            prompt,
            context: value
                .get("context")
                .and_then(Value::as_str)
                .map(str::to_string),
            mode,
            allowed_tools,
            max_iterations: value
                .get("max_iterations")
                .and_then(Value::as_u64)
                .map(|v| v as usize),
            timeout_secs: value.get("timeout_secs").and_then(Value::as_u64),
            plan_item_id: value
                .get("plan_item_id")
                .and_then(Value::as_str)
                .map(str::to_string),
            session_id: None,
        })
    }

    async fn handle_run(
        &self,
        args: &Value,
        context: &ToolExecutionContext,
    ) -> anyhow::Result<ToolResult> {
        let requested_mode = match args
            .get("mode")
            .and_then(Value::as_str)
            .unwrap_or("foreground")
        {
            "foreground" => ExecutionMode::Foreground,
            "background" => ExecutionMode::Background,
            other => {
                return Ok(failure(format!(
                    "unknown mode '{other}'; supported modes are foreground and background"
                )));
            }
        };
        let background_downgraded =
            requested_mode == ExecutionMode::Background && context.execution_origin.is_scheduled();
        let mode = if background_downgraded {
            ExecutionMode::Foreground
        } else {
            requested_mode
        };
        let task_values: Vec<&Value> = match args.get("tasks").and_then(Value::as_array) {
            Some(tasks) if !tasks.is_empty() => tasks.iter().collect(),
            Some(_) => return Ok(failure("tasks must not be empty")),
            None => vec![args],
        };
        let mut configs = Vec::with_capacity(task_values.len());
        for task in task_values {
            let mut config = self.parse_config(task, mode.clone())?;
            if config.target.is_none() {
                config.target = args
                    .get("target")
                    .and_then(Value::as_str)
                    .map(str::to_string);
            }
            if config.context.is_none() {
                config.context = args
                    .get("context")
                    .and_then(Value::as_str)
                    .map(str::to_string);
            }
            if config.allowed_tools.is_none() {
                config.allowed_tools =
                    args.get("allowed_tools")
                        .and_then(Value::as_array)
                        .map(|items| {
                            items
                                .iter()
                                .filter_map(Value::as_str)
                                .map(str::to_string)
                                .collect()
                        });
            }
            config.max_iterations = config.max_iterations.or_else(|| {
                args.get("max_iterations")
                    .and_then(Value::as_u64)
                    .map(|v| v as usize)
            });
            config.timeout_secs = config
                .timeout_secs
                .or_else(|| args.get("timeout_secs").and_then(Value::as_u64));
            config.session_id = context
                .agent
                .as_ref()
                .map(|agent| agent.root_session_id.clone())
                .or_else(|| context.session_id.clone());
            configs.push(config);
        }

        match mode {
            ExecutionMode::Foreground => {
                let Some(coordinator) = self.coordinator.as_ref() else {
                    return Ok(failure(
                        "delegate requires agent_orchestration to be enabled (named Agents only)",
                    ));
                };
                let results = coordinator
                    .delegate_foreground(context, configs)
                    .await
                    .map_err(|error| anyhow::anyhow!(error.to_string()))?;
                let payload: Vec<_> = results
                    .into_iter()
                    .map(|result| {
                        let (status, error) = status_projection(&result.status);
                        json!({
                            "run_id": result.task_id,
                            "status": status,
                            "result": result.content,
                            "result_truncated": result.content_truncated,
                            "error": error,
                            "tool_calls": result.tool_calls_count,
                            "iterations": result.iterations,
                            "duration_ms": result.duration_ms
                        })
                    })
                    .collect();
                let all_completed = payload.iter().all(|value| value["status"] == "completed");
                Ok(ToolResult {
                    success: all_completed,
                    output: serde_json::to_string(&json!({
                        "status": if all_completed { "completed" } else { "partial" },
                        "background_downgraded": background_downgraded,
                        "results": payload
                    }))?,
                    error: None,
                })
            }
            ExecutionMode::Background => {
                if context.agent.is_some() {
                    return Ok(failure(
                        "child Agents cannot create background runs in the current implementation",
                    ));
                }
                let Some(coordinator) = self.coordinator.as_ref() else {
                    return Ok(failure(
                        "delegate requires agent_orchestration to be enabled (named Agents only)",
                    ));
                };
                match coordinator.delegate_background(context, configs).await {
                    Ok(admission) => {
                        let runs: Vec<_> = admission
                            .run_ids
                            .into_iter()
                            .map(|run_id| json!({ "run_id": run_id, "status": "queued" }))
                            .collect();
                        Ok(success(json!({
                            "status": "accepted",
                            "runs": runs
                        })))
                    }
                    Err(error) => Ok(failure(error.to_string())),
                }
            }
        }
    }
}

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

    fn description(&self) -> &str {
        "Delegate one or more independent tasks to configured Agents. foreground waits for all results; background returns accepted run IDs. Multiple tasks execute concurrently."
    }

    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "target": self.task_schema()["properties"]["target"].clone(),
                "task": { "type": "string", "description": "Single delegated task" },
                "context": { "type": "string", "description": "Explicit context for the child Agent" },
                "mode": {
                    "type": "string",
                    "enum": ["foreground", "background"],
                    "description": "foreground waits; background returns after acceptance"
                },
                "tasks": {
                    "type": "array",
                    "minItems": 1,
                    "items": self.task_schema(),
                    "description": "Independent tasks; execution is concurrent and results preserve request order"
                },
                "plan_item_id": { "type": "string" },
                "allowed_tools": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Only narrows the target Definition's tool set; never expands permissions"
                },
                "max_iterations": { "type": "integer", "minimum": 1 },
                "timeout_secs": { "type": "integer", "minimum": 1 }
            },
            "anyOf": [
                { "required": ["task"] },
                { "required": ["tasks"] }
            ]
        })
    }

    fn read_only(&self) -> bool {
        false
    }

    fn runtime_injected(&self) -> bool {
        true
    }

    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
        self.execute_with_context(&ToolExecutionContext::default(), args)
            .await
            .map(|output| output.result)
    }

    async fn execute_with_context(
        &self,
        context: &ToolExecutionContext,
        args: Value,
    ) -> anyhow::Result<ToolOutput> {
        self.handle_run(&args, context).await.map(Into::into)
    }
}

fn status_projection(status: &TaskStatus) -> (&'static str, Option<&str>) {
    match status {
        TaskStatus::Completed => ("completed", None),
        TaskStatus::Failed(error) => ("failed", Some(error.as_str())),
        TaskStatus::Cancelled => ("cancelled", None),
        TaskStatus::TimedOut => ("timed_out", None),
    }
}

fn success(value: Value) -> ToolResult {
    ToolResult {
        success: true,
        output: value.to_string(),
        error: None,
    }
}

fn failure(error: impl Into<String>) -> ToolResult {
    ToolResult {
        success: false,
        output: String::new(),
        error: Some(error.into()),
    }
}

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

    fn manager() -> Arc<SubAgentManager> {
        Arc::new(SubAgentManager::new(
            crate::config::LLMProviderConfig {
                provider_type: "openai".to_string(),
                name: "test".to_string(),
                base_url: "https://example.invalid/v1".to_string(),
                api_key: "test".to_string(),
                extra_headers: HashMap::new(),
                model_id: "test-model".to_string(),
                temperature: None,
                max_tokens: None,
                model_extra: HashMap::new(),
                max_tool_iterations: 1,
                token_limit: 4096,
                workspace_dir: std::env::temp_dir(),
                input_types: vec!["text".to_string()],
                price_input_per_million: None,
                price_output_per_million: None,
            },
            Arc::new(crate::tools::ToolRegistry::new()),
            None,
            None,
        ))
    }

    #[test]
    fn schema_exposes_only_canonical_lifecycle_modes() {
        let schema = DelegateTool::new(manager()).parameters_schema();
        assert_eq!(
            schema["properties"]["mode"]["enum"],
            json!(["foreground", "background"])
        );
    }

    #[test]
    fn scoped_schema_replaces_root_targets_for_child() {
        let inner: Arc<dyn Tool> = Arc::new(DelegateTool::new(manager()));
        let scoped = ScopedDelegateTool::new(inner, vec!["reviewer".to_string()]);
        let schema = scoped.parameters_schema();
        assert_eq!(schema["properties"]["target"]["enum"], json!(["reviewer"]));
        assert_eq!(
            schema["properties"]["tasks"]["items"]["properties"]["target"]["enum"],
            json!(["reviewer"])
        );
    }
}
