use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::providers::ToolCall;

/// Provider-private state required to faithfully replay an assistant message.
///
/// This is durable conversation data, but it is never presentation data. UI and
/// channel projections must not serialize it to end users.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProviderReasoningState {
    pub provider: String,
    pub payload: serde_json::Value,
}

impl ProviderReasoningState {
    /// Decode persisted provider state without making conversation history
    /// unreadable when an old or damaged payload is encountered.
    pub fn from_json_lossy(value: &str) -> Option<Self> {
        serde_json::from_str(value).ok()
    }
}

/// Describes whether a persisted message represents a complete model result.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompletionStatus {
    #[default]
    Completed,
    Cancelled,
    Interrupted,
}

impl CompletionStatus {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Completed => "completed",
            Self::Cancelled => "cancelled",
            Self::Interrupted => "interrupted",
        }
    }

    pub fn from_storage(value: &str) -> Self {
        match value {
            "cancelled" => Self::Cancelled,
            "interrupted" => Self::Interrupted,
            _ => Self::Completed,
        }
    }
}

// ============================================================================
// ContentBlock - Multimodal content representation (OpenAI-style)
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
    #[serde(rename = "text")]
    Text { text: String },
    #[serde(rename = "image_url")]
    ImageUrl { image_url: ImageUrlBlock },
}

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

impl ContentBlock {
    pub fn text(content: impl Into<String>) -> Self {
        Self::Text {
            text: content.into(),
        }
    }

    pub fn image_url(url: impl Into<String>) -> Self {
        Self::ImageUrl {
            image_url: ImageUrlBlock { url: url.into() },
        }
    }
}

// ============================================================================
// MediaRef - Media reference in ChatMessage (carries type info)
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MediaRef {
    pub path: String,
    pub media_type: String,
}

// ============================================================================
// MediaItem - Media metadata for messages
// ============================================================================

#[derive(Debug, Clone)]
pub struct MediaItem {
    pub path: String,       // Local file path
    pub media_type: String, // "image", "audio", "file", "video"
    pub mime_type: Option<String>,
    pub original_key: Option<String>, // Feishu file_key for download
}

impl MediaItem {
    pub fn new(path: impl Into<String>, media_type: impl Into<String>) -> Self {
        Self {
            path: path.into(),
            media_type: media_type.into(),
            mime_type: None,
            original_key: None,
        }
    }

    pub fn to_media_ref(&self) -> MediaRef {
        MediaRef {
            path: self.path.clone(),
            media_type: self.media_type.clone(),
        }
    }

    pub fn from_media_ref(media_ref: &MediaRef) -> Self {
        Self::new(media_ref.path.clone(), media_ref.media_type.clone())
    }
}

// ============================================================================
// ChatMessage - Used by AgentLoop for LLM conversation history
// ============================================================================

/// Whether a message may be surfaced to clients.  Hidden messages exist only
/// for model replay (internal triggers) and must never appear in history,
/// projections or delivery.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ClientVisibility {
    #[default]
    Visible,
    Hidden,
}

impl ClientVisibility {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Visible => "visible",
            Self::Hidden => "hidden",
        }
    }
}

/// Where a message Turn originated.  Persisted alongside the message so
/// clients can render agent-driven continuation output without treating it as
/// a user bubble.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum TurnOrigin {
    #[default]
    User,
    AgentContinuation,
    Scheduled,
}

impl TurnOrigin {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::User => "user",
            Self::AgentContinuation => "agent_continuation",
            Self::Scheduled => "scheduled",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
    pub id: String,
    pub role: String,
    pub content: String,
    pub reasoning_content: Option<String>,
    /// Opaque state used only when replaying history to the same provider.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub provider_state: Option<ProviderReasoningState>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub turn_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub iteration: Option<u32>,
    #[serde(default)]
    pub completion_status: CompletionStatus,
    #[serde(default)]
    pub client_visibility: ClientVisibility,
    #[serde(default)]
    pub turn_origin: TurnOrigin,
    pub media_refs: Vec<MediaRef>,
    pub timestamp: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<ToolCall>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source: Option<MessageSource>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SourceKind {
    #[serde(rename = "user_input")]
    UserInput,
    #[serde(rename = "system_notification")]
    SystemNotification,
    #[serde(rename = "cross_channel")]
    CrossChannel,
    #[serde(rename = "external_trigger")]
    ExternalTrigger,
    /// A durable signal emitted by a background Agent via `emit_signal`.
    #[serde(rename = "agent_signal")]
    AgentSignal,
    /// A durable background run completion outcome.
    #[serde(rename = "agent_result")]
    AgentCompletion,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageSource {
    pub kind: SourceKind,
    pub from_channel: Option<String>,
    pub from_session: Option<String>,
    pub from_user_id: Option<String>,
    pub system_name: Option<String>,
    pub task_id: Option<String>,
    /// Durable Agent run identity for `agent_signal`/`agent_result` sources.
    #[serde(default)]
    pub from_run_id: Option<String>,
    /// Agent definition id for `agent_signal`/`agent_result` sources.
    #[serde(default)]
    pub from_agent_id: Option<String>,
}

impl ChatMessage {
    pub fn user(content: impl Into<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "user".to_string(),
            content: content.into(),
            reasoning_content: None,
            provider_state: None,
            turn_id: None,
            iteration: None,
            completion_status: CompletionStatus::Completed,
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            tool_call_id: None,
            tool_name: None,
            tool_calls: None,
            source: None,
            client_visibility: ClientVisibility::Visible,
            turn_origin: TurnOrigin::User,
        }
    }

    pub fn user_with_media(content: impl Into<String>, media_refs: Vec<MediaRef>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "user".to_string(),
            content: content.into(),
            reasoning_content: None,
            provider_state: None,
            turn_id: None,
            iteration: None,
            completion_status: CompletionStatus::Completed,
            media_refs,
            timestamp: current_timestamp(),
            tool_call_id: None,
            tool_name: None,
            tool_calls: None,
            source: None,
            client_visibility: ClientVisibility::Visible,
            turn_origin: TurnOrigin::User,
        }
    }

    pub fn assistant(content: impl Into<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "assistant".to_string(),
            content: content.into(),
            reasoning_content: None,
            provider_state: None,
            turn_id: None,
            iteration: None,
            completion_status: CompletionStatus::Completed,
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            tool_call_id: None,
            tool_name: None,
            tool_calls: None,
            source: None,
            client_visibility: ClientVisibility::Visible,
            turn_origin: TurnOrigin::User,
        }
    }

    pub fn assistant_with_tool_calls(
        content: impl Into<String>,
        tool_calls: Vec<ToolCall>,
    ) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "assistant".to_string(),
            content: content.into(),
            reasoning_content: None,
            provider_state: None,
            turn_id: None,
            iteration: None,
            completion_status: CompletionStatus::Completed,
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            tool_call_id: None,
            tool_name: None,
            tool_calls: Some(tool_calls),
            source: None,
            client_visibility: ClientVisibility::Visible,
            turn_origin: TurnOrigin::User,
        }
    }

    pub fn assistant_with_source(content: impl Into<String>, source: MessageSource) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "assistant".to_string(),
            content: content.into(),
            reasoning_content: None,
            provider_state: None,
            turn_id: None,
            iteration: None,
            completion_status: CompletionStatus::Completed,
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            tool_call_id: None,
            tool_name: None,
            tool_calls: None,
            source: Some(source),
            client_visibility: ClientVisibility::Visible,
            turn_origin: TurnOrigin::User,
        }
    }

    pub fn system(content: impl Into<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "system".to_string(),
            content: content.into(),
            reasoning_content: None,
            provider_state: None,
            turn_id: None,
            iteration: None,
            completion_status: CompletionStatus::Completed,
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            tool_call_id: None,
            tool_name: None,
            tool_calls: None,
            source: None,
            client_visibility: ClientVisibility::Visible,
            turn_origin: TurnOrigin::User,
        }
    }

    pub fn tool(
        tool_call_id: impl Into<String>,
        tool_name: impl Into<String>,
        content: impl Into<String>,
    ) -> Self {
        Self::tool_with_media(tool_call_id, tool_name, content, Vec::new())
    }

    pub fn tool_with_media(
        tool_call_id: impl Into<String>,
        tool_name: impl Into<String>,
        content: impl Into<String>,
        media_refs: Vec<MediaRef>,
    ) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "tool".to_string(),
            content: content.into(),
            reasoning_content: None,
            provider_state: None,
            turn_id: None,
            iteration: None,
            completion_status: CompletionStatus::Completed,
            media_refs,
            timestamp: current_timestamp(),
            tool_call_id: Some(tool_call_id.into()),
            tool_name: Some(tool_name.into()),
            tool_calls: None,
            source: None,
            client_visibility: ClientVisibility::Visible,
            turn_origin: TurnOrigin::User,
        }
    }

    pub fn user_with_source(content: impl Into<String>, source: MessageSource) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "user".to_string(),
            content: content.into(),
            reasoning_content: None,
            provider_state: None,
            turn_id: None,
            iteration: None,
            completion_status: CompletionStatus::Completed,
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            tool_call_id: None,
            tool_name: None,
            tool_calls: None,
            source: Some(source),
            client_visibility: ClientVisibility::Visible,
            turn_origin: TurnOrigin::User,
        }
    }
}

#[cfg(test)]
mod conversation_message_tests {
    use super::*;

    #[test]
    fn damaged_provider_state_is_ignored() {
        assert!(ProviderReasoningState::from_json_lossy("not-json").is_none());
    }

    #[test]
    fn unknown_completion_status_is_backward_compatible() {
        assert_eq!(
            CompletionStatus::from_storage("future-status"),
            CompletionStatus::Completed
        );
    }
}

// ============================================================================
// InboundMessage - Message from Channel to Bus (user input)
// ============================================================================

/// Opaque channel-owned context that may be carried to the corresponding reply.
/// Core routing understands `reply_to`; all other platform data remains
/// private.  `durable_private` holds only values the channel declares safe to
/// reuse across turns (thread/root identity); one-shot message/reaction ids
/// belong in `private`.
#[derive(Debug, Clone, Default)]
pub struct ChannelContext {
    pub reply_to: Option<String>,
    pub private: HashMap<String, String>,
    pub durable_private: HashMap<String, String>,
}

/// Public, durable projection of a newly committed conversation message.
/// Provider replay state and source identities are deliberately excluded.
#[derive(Debug, Clone)]
pub struct CommittedMessage {
    pub id: String,
    pub seq: i64,
    pub role: String,
    pub content: String,
    pub reasoning_content: Option<String>,
    pub completion_status: CompletionStatus,
    pub media_refs: Vec<MediaRef>,
    pub created_at: i64,
    pub tool_call_id: Option<String>,
    pub tool_name: Option<String>,
    pub tool_calls: Option<Vec<ToolCall>>,
    pub turn_origin: TurnOrigin,
}

#[derive(Debug, Clone)]
pub struct CommittedTurnDelta {
    pub session_id: String,
    /// Highest durable message sequence included in this commit.
    pub history_revision: i64,
    pub messages: Vec<CommittedMessage>,
}

#[derive(Debug, Clone)]
pub struct InboundMessage {
    pub channel: String,
    pub sender_id: String,
    pub chat_id: String,
    /// Client-provided id for optimistic UI reconciliation.  Channel-owned
    /// inputs that do not expose a client id leave this unset; the session
    /// layer may generate a durable id when it accepts the message.
    pub client_message_id: Option<String>,
    pub content: String,
    pub received_at: i64,
    pub media: Vec<MediaItem>,
    pub channel_context: ChannelContext,
}

// ============================================================================
// OutboundMessage - Message from Agent to Channel (bot response)
// ============================================================================

#[derive(Debug, Clone)]
pub struct OutboundMessage {
    pub channel: String,
    pub chat_id: String,
    pub content: String,
    pub reply_to: Option<String>,
    pub media: Vec<MediaItem>,
    pub metadata: HashMap<String, String>,
    pub(crate) delivery: Option<tokio::sync::watch::Sender<Option<DeliveryReceipt>>>,
}

impl OutboundMessage {
    pub(crate) fn complete_delivery(&self, result: DeliveryReceipt) {
        if let Some(delivery) = &self.delivery {
            delivery.send_replace(Some(result));
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeliveryReceipt {
    Delivered,
    TransientFailure { summary: String },
    PermanentFailure { summary: String },
    TimedOut,
    DispatcherClosed,
}

// ============================================================================
// ControlMessage - Message for control channel (session management)
// Uses SessionCommand from session module
// ============================================================================

use crate::channels::base::ChannelError;
use crate::session::{SessionCommand, SessionEvent};
use tokio::sync::mpsc;

/// Control message containing a session operation and reply channel
#[derive(Debug, Clone)]
pub struct ControlMessage {
    pub op: SessionCommand,
    pub reply_tx: mpsc::Sender<Result<SessionEvent, ChannelError>>,
}

// ============================================================================
// Helpers
// ============================================================================

pub(crate) fn current_timestamp() -> i64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_millis() as i64
}
