use std::time::Instant;

use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio::sync::mpsc;
use uuid::Uuid;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AgentRealtime {
    pub channel: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AgentRunContext {
    pub conversation_id: Option<Uuid>,
    pub invocation_id: Option<Uuid>,
    pub session_id: Option<Uuid>,
    pub user_id: Option<Uuid>,
    pub realtime: Option<AgentRealtime>,
}

impl AgentRunContext {
    pub fn new(user_id: Uuid) -> Self {
        Self {
            conversation_id: None,
            invocation_id: None,
            session_id: None,
            user_id: Some(user_id),
            realtime: None,
        }
    }

    pub fn with_conversation_id(mut self, id: Uuid) -> Self {
        self.conversation_id = Some(id);
        self
    }

    pub fn with_invocation_id(mut self, id: Uuid) -> Self {
        self.invocation_id = Some(id);
        self
    }

    pub fn with_session_id(mut self, id: Uuid) -> Self {
        self.session_id = Some(id);
        self
    }

    pub fn with_realtime(mut self, realtime: AgentRealtime) -> Self {
        self.realtime = Some(realtime);
        self
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AgentStreamEvent {
    Started {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        session_id: Option<Uuid>,
        model: String,
    },
    Delta {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        index: usize,
        content: String,
    },
    Thinking {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        index: usize,
        content: String,
    },
    ToolCallStarted {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        session_id: Option<Uuid>,
        tool_call_id: String,
        tool_name: String,
        arguments: Value,
    },
    ToolCallFinished {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        session_id: Option<Uuid>,
        tool_call_id: String,
        tool_name: String,
        output: Option<Value>,
        error: Option<String>,
        execution_time_ms: i64,
    },
    SubagentStarted {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        subagent_id: String,
        role: String,
        task: String,
        model: String,
    },
    SubagentDelta {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        subagent_id: String,
        index: usize,
        content: String,
    },
    SubagentCompleted {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        subagent_id: String,
        role: String,
        task: String,
        output: String,
        input_tokens: i64,
        output_tokens: i64,
        model: String,
    },
    SubagentFailed {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        subagent_id: String,
        error: String,
    },
    Completed {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        session_id: Option<Uuid>,
        output: String,
        input_tokens: i64,
        output_tokens: i64,
        latency_ms: i32,
        stop_reason: Option<String>,
    },
    Failed {
        conversation_id: Option<Uuid>,
        message_id: Option<Uuid>,
        session_id: Option<Uuid>,
        error: String,
    },
}

#[derive(Clone)]
pub struct AgentRuntime {
    pub tx: Option<mpsc::UnboundedSender<String>>,
}

impl AgentRuntime {
    pub fn new(tx: mpsc::UnboundedSender<String>) -> Self {
        Self { tx: Some(tx) }
    }

    pub fn empty() -> Self {
        Self { tx: None }
    }
}

impl Default for AgentRuntime {
    fn default() -> Self {
        Self::empty()
    }
}

#[derive(Clone, Debug)]
pub struct ActiveAgentRun {
    pub conversation_id: Option<Uuid>,
    pub message_id: Option<Uuid>,
    pub invocation_id: Option<Uuid>,
    pub session_id: Option<Uuid>,
    pub user_id: Option<Uuid>,
    pub started_at: Instant,
    pub current_step: usize,
}

pub fn estimate_output_tokens(output: &str) -> i64 {
    (output.chars().count() as f64 / 4.0).ceil() as i64
}
