use std::sync::Arc;

use agent_contracts::runtime::RuntimeView;
use agent_contracts::tool::{ToolExecutor, ToolSpecView};
use agent_types::tool::{FinalToolCall, RawToolOutcome, ToolExecutionError, ToolExecutorOutput};
use async_trait::async_trait;
use subagent::SpawnSubagentRequest;

use crate::r#impl::ToolRuntimeServices;

use super::input::SpawnSubagentInput;
use super::output::SpawnSubagentOutput;
use super::spec::SpawnSubagentToolSpec;
use super::validation;

pub struct SpawnSubagentExecutor {
    spec: Arc<SpawnSubagentToolSpec>,
    services: ToolRuntimeServices,
}

impl SpawnSubagentExecutor {
    pub fn new(spec: Arc<SpawnSubagentToolSpec>, services: ToolRuntimeServices) -> Self {
        Self { spec, services }
    }
}

#[async_trait]
impl ToolExecutor for SpawnSubagentExecutor {
    fn spec(&self) -> &dyn ToolSpecView {
        self.spec.as_ref()
    }

    async fn invoke(
        &self,
        call: &FinalToolCall,
        runtime: &dyn RuntimeView,
    ) -> Result<ToolExecutorOutput, ToolExecutionError> {
        let input: SpawnSubagentInput =
            serde_json::from_value(call.input.clone()).map_err(|error| {
                ToolExecutionError::ExecutionFailed {
                    message: format!("Failed to parse input: {}", error),
                }
            })?;

        let validation_result = validation::validate_input(&input);
        if !validation_result.result {
            let message = validation_result
                .message
                .unwrap_or_else(|| "Validation failed".to_string());
            let error_code = validation_result.error_code.unwrap_or(0);
            return Ok(ToolExecutorOutput::Completed {
                raw_outcome: RawToolOutcome::Error {
                    message: format!("[error_code={}] {}", error_code, message),
                },
            });
        }

        let subagent_control = self.services.subagent_control.clone().ok_or_else(|| {
            ToolExecutionError::ExecutionFailed {
                message: "subagent control is not configured".to_string(),
            }
        })?;
        let session_id = runtime
            .agent_context()
            .metadata()
            .session_id
            .clone()
            .ok_or_else(|| ToolExecutionError::ExecutionFailed {
                message: "spawn_subagent requires a session_id in runtime metadata".to_string(),
            })?;
        let parent_agent_id =
            agent_types::common::ids::AgentId(runtime.agent_context().metadata().agent_id.clone());

        let (description, task_goal, task_context) = if let Some(role_id) = &input.subagent_role_id
        {
            let role_config = self.services.subagent_roles.get(role_id);
            if let Some(config) = role_config {
                let goal = config.prompt.clone().unwrap_or(input.task_goal.clone());
                let context = if config.description.is_empty() {
                    input.task_context.clone()
                } else {
                    format!("{}\n\n{}", config.description, input.task_context)
                };
                (input.description.clone(), goal, context)
            } else {
                (
                    input.description.clone(),
                    input.task_goal.clone(),
                    input.task_context.clone(),
                )
            }
        } else {
            (
                input.description.clone(),
                input.task_goal.clone(),
                input.task_context.clone(),
            )
        };

        let result = subagent_control
            .spawn(SpawnSubagentRequest {
                session_id,
                parent_agent_id,
                description,
                task_goal,
                task_context,
                output_schema: input.output_schema,
                subagent_role_id: input.subagent_role_id,
                predefined_prompt: None,
                max_turns: Some(input.max_turns.unwrap_or(20)),
            })
            .await
            .map_err(|error| ToolExecutionError::ExecutionFailed {
                message: error.to_string(),
            })?;

        let output = serde_json::to_string(&SpawnSubagentOutput {
            agent_id: result.agent_id,
        })
        .map_err(|error| ToolExecutionError::ExecutionFailed {
            message: format!("Failed to serialize spawn_subagent output: {}", error),
        })?;

        Ok(ToolExecutorOutput::Completed {
            raw_outcome: RawToolOutcome::Success { output },
        })
    }
}