use crate::api::provider::TokenUsage;
use crate::api::{ApiError, StreamError};
use crate::app::conversation::UserContent;
use crate::app::domain::types::{CompactionId, MessageId, OpId, RequestId, SessionId, ToolCallId};
use crate::config::model::ModelId;
use serde::{Deserialize, Serialize};
use steer_tools::result::ToolResult;
use steer_tools::{ToolCall, ToolError, ToolSchema};
use thiserror::Error;

use super::event::SessionEvent;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelCallRequestErrorKind {
    Network,
    AuthenticationFailed,
    AuthError,
    RateLimited,
    InvalidRequest,
    ServerError,
    Timeout,
    Cancelled,
    ResponseParsingError,
    NoChoices,
    RequestBlocked,
    Unknown,
    Configuration,
    UnsupportedFeature,
    StreamError,
}

impl ModelCallRequestErrorKind {
    pub const fn from_api_error(error: &ApiError) -> Self {
        match error {
            ApiError::Network(_) => Self::Network,
            ApiError::AuthenticationFailed { .. } => Self::AuthenticationFailed,
            ApiError::AuthError(_) => Self::AuthError,
            ApiError::RateLimited { .. } => Self::RateLimited,
            ApiError::InvalidRequest { .. } => Self::InvalidRequest,
            ApiError::ServerError { .. } => Self::ServerError,
            ApiError::Timeout { .. } => Self::Timeout,
            ApiError::Cancelled { .. } => Self::Cancelled,
            ApiError::ResponseParsingError { .. } => Self::ResponseParsingError,
            ApiError::NoChoices { .. } => Self::NoChoices,
            ApiError::RequestBlocked { .. } => Self::RequestBlocked,
            ApiError::Unknown { .. } => Self::Unknown,
            ApiError::Configuration(_) => Self::Configuration,
            ApiError::UnsupportedFeature { .. } => Self::UnsupportedFeature,
            ApiError::StreamError { .. } => Self::StreamError,
        }
    }
}

impl std::fmt::Display for ModelCallRequestErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let kind = match self {
            Self::Network => "network",
            Self::AuthenticationFailed => "authentication_failed",
            Self::AuthError => "auth_error",
            Self::RateLimited => "rate_limited",
            Self::InvalidRequest => "invalid_request",
            Self::ServerError => "server_error",
            Self::Timeout => "timeout",
            Self::Cancelled => "cancelled",
            Self::ResponseParsingError => "response_parsing_error",
            Self::NoChoices => "no_choices",
            Self::RequestBlocked => "request_blocked",
            Self::Unknown => "unknown",
            Self::Configuration => "configuration",
            Self::UnsupportedFeature => "unsupported_feature",
            Self::StreamError => "stream_error",
        };

        f.write_str(kind)
    }
}

#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum ModelCallError {
    #[error("Failed to start model stream ({kind}): {message}")]
    RequestStartFailed {
        kind: ModelCallRequestErrorKind,
        message: String,
    },

    #[error("Model stream failed: {0}")]
    StreamFailed(StreamError),

    #[error("Model stream ended without completion response")]
    MissingCompletionResponse,
}

#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum SessionTitleGenerationError {
    #[error(transparent)]
    ModelCall(#[from] ModelCallError),
}

#[derive(Debug, Clone)]
pub enum Action {
    UserInput {
        session_id: SessionId,
        content: Vec<UserContent>,
        op_id: OpId,
        message_id: MessageId,
        model: ModelId,
        timestamp: u64,
    },

    UserEditedMessage {
        session_id: SessionId,
        message_id: MessageId,
        new_content: Vec<UserContent>,
        op_id: OpId,
        new_message_id: MessageId,
        model: ModelId,
        timestamp: u64,
    },

    ToolApprovalRequested {
        session_id: SessionId,
        request_id: RequestId,
        tool_call: ToolCall,
    },

    ToolApprovalDecided {
        session_id: SessionId,
        request_id: RequestId,
        decision: ApprovalDecision,
        remember: Option<ApprovalMemory>,
    },

    ToolExecutionStarted {
        session_id: SessionId,
        tool_call_id: ToolCallId,
        tool_name: String,
        tool_parameters: serde_json::Value,
    },

    ToolResult {
        session_id: SessionId,
        tool_call_id: ToolCallId,
        tool_name: String,
        result: Result<ToolResult, ToolError>,
    },

    ToolSchemasAvailable {
        session_id: SessionId,
        tools: Vec<ToolSchema>,
    },

    ToolSchemasUpdated {
        session_id: SessionId,
        schemas: Vec<ToolSchema>,
        source: SchemaSource,
    },

    SwitchPrimaryAgent {
        session_id: SessionId,
        agent_id: String,
    },

    McpServerStateChanged {
        session_id: SessionId,
        server_name: String,
        state: McpServerState,
    },

    ModelResponseComplete {
        session_id: SessionId,
        op_id: OpId,
        message_id: MessageId,
        content: Vec<crate::app::conversation::AssistantContent>,
        usage: Option<TokenUsage>,
        context_window_tokens: Option<u32>,
        configured_max_output_tokens: Option<u32>,
        timestamp: u64,
    },

    ModelResponseError {
        session_id: SessionId,
        op_id: OpId,
        error: String,
    },

    SessionTitleGenerated {
        session_id: SessionId,
        title: String,
    },

    SessionTitleGenerationFailed {
        session_id: SessionId,
        error: SessionTitleGenerationError,
    },

    Cancel {
        session_id: SessionId,
        op_id: Option<OpId>,
    },

    DirectBashCommand {
        session_id: SessionId,
        op_id: OpId,
        message_id: MessageId,
        command: String,
        timestamp: u64,
    },

    DequeueQueuedItem {
        session_id: SessionId,
    },

    DrainQueuedWork {
        session_id: SessionId,
    },

    RequestCompaction {
        session_id: SessionId,
        op_id: OpId,
        model: ModelId,
    },

    Shutdown,

    Hydrate {
        session_id: SessionId,
        events: Vec<SessionEvent>,
        starting_sequence: u64,
    },

    WorkspaceFilesListed {
        session_id: SessionId,
        files: Vec<String>,
    },

    CompactionComplete {
        session_id: SessionId,
        op_id: OpId,
        compaction_id: CompactionId,
        summary_message_id: MessageId,
        summary: String,
        compacted_head_message_id: MessageId,
        previous_active_message_id: Option<MessageId>,
        model: String,
        timestamp: u64,
    },

    CompactionFailed {
        session_id: SessionId,
        op_id: OpId,
        error: String,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ApprovalDecision {
    Approved,
    Denied,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ApprovalMemory {
    Tool(String),
    BashPattern(String),
    PendingTool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SchemaSource {
    Workspace,
    Mcp { server_name: String },
    Backend { backend_name: String },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum McpServerState {
    Connecting,
    Connected { tools: Vec<ToolSchema> },
    Disconnected { error: Option<String> },
    Failed { error: String },
}

impl Action {
    pub fn session_id(&self) -> Option<SessionId> {
        match self {
            Action::UserInput { session_id, .. }
            | Action::UserEditedMessage { session_id, .. }
            | Action::ToolApprovalRequested { session_id, .. }
            | Action::ToolApprovalDecided { session_id, .. }
            | Action::ToolExecutionStarted { session_id, .. }
            | Action::ToolResult { session_id, .. }
            | Action::ToolSchemasAvailable { session_id, .. }
            | Action::ToolSchemasUpdated { session_id, .. }
            | Action::SwitchPrimaryAgent { session_id, .. }
            | Action::McpServerStateChanged { session_id, .. }
            | Action::ModelResponseComplete { session_id, .. }
            | Action::ModelResponseError { session_id, .. }
            | Action::SessionTitleGenerated { session_id, .. }
            | Action::SessionTitleGenerationFailed { session_id, .. }
            | Action::Cancel { session_id, .. }
            | Action::DirectBashCommand { session_id, .. }
            | Action::DequeueQueuedItem { session_id, .. }
            | Action::DrainQueuedWork { session_id, .. }
            | Action::RequestCompaction { session_id, .. }
            | Action::Hydrate { session_id, .. }
            | Action::WorkspaceFilesListed { session_id, .. }
            | Action::CompactionComplete { session_id, .. }
            | Action::CompactionFailed { session_id, .. } => Some(*session_id),
            Action::Shutdown => None,
        }
    }

    pub fn op_id(&self) -> Option<OpId> {
        match self {
            Action::UserInput { op_id, .. }
            | Action::UserEditedMessage { op_id, .. }
            | Action::DirectBashCommand { op_id, .. }
            | Action::RequestCompaction { op_id, .. }
            | Action::ModelResponseComplete { op_id, .. }
            | Action::ModelResponseError { op_id, .. }
            | Action::CompactionComplete { op_id, .. }
            | Action::CompactionFailed { op_id, .. } => Some(*op_id),
            Action::Cancel { op_id, .. } => *op_id,
            _ => None,
        }
    }
}
