//! LLM Gateway Core Types (Phase-78)
//!
//! Defines the data models for the unified LLM Gateway:
//!
//! - **LlmProvider**: Provider registration (Ollama, OpenAI, DeepSeek, etc.)
//! - **LlmModel**: Model registration (per-provider, with capabilities + pricing)
//! - **ProviderBrand**: Brand enumeration (extensible via Custom variant)
//! - **ProviderProtocol**: API protocol (OpenAI-compat / Gemini / Anthropic / Custom)
//! - **ProviderCapabilities**: Capability declarations (streaming, vision, audio, etc.)
//! - **StreamEvent**: Unified streaming event types (10 event kinds)
//! - **ChatConfig**: Per-request configuration
//! - **ChatResponse**: Non-streaming response

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

// ============================================================================
// Provider Brand
// ============================================================================

/// LLM Provider brand enumeration.
///
/// Covers major providers with dedicated protocol/config handling.
/// Use `Custom(String)` for any unlisted provider.
///
/// # Design note
///
/// Unlike chat_api's hardcoded 5 brands, this is extensible at runtime
/// via the `Custom` variant. The brand primarily determines the default
/// protocol and capability set.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProviderBrand {
    /// Ollama (local, OpenAI-compatible)
    Ollama,
    /// OpenAI (GPT-4o, o1, etc.)
    OpenAI,
    /// DeepSeek (DeepSeek-V3, DeepSeek-R1, etc.)
    DeepSeek,
    /// Alibaba Qwen (Qwen2.5, QwQ, etc.)
    Qwen,
    /// Google Gemini (native protocol)
    Gemini,
    /// Anthropic Claude (Messages API protocol)
    Anthropic,
    /// OpenRouter (multi-provider aggregator, OpenAI-compatible)
    OpenRouter,
    /// Azure OpenAI (OpenAI-compatible with API version)
    AzureOpenAI,
    /// MicroGPT (ai_db built-in local inference via Candle)
    MicroGpt,
    /// Custom provider (runtime-registered, extensible)
    Custom(String),
}

impl ProviderBrand {
    /// Returns the default protocol for this brand.
    pub fn default_protocol(&self) -> ProviderProtocol {
        match self {
            Self::Gemini => ProviderProtocol::Gemini,
            Self::Anthropic => ProviderProtocol::Anthropic,
            Self::MicroGpt => ProviderProtocol::Custom,
            Self::Custom(_) => ProviderProtocol::OpenAiCompat,
            // All others use OpenAI-compatible protocol
            _ => ProviderProtocol::OpenAiCompat,
        }
    }

    /// Returns a human-readable display name for the brand.
    pub fn display_name(&self) -> &str {
        match self {
            Self::Ollama => "Ollama",
            Self::OpenAI => "OpenAI",
            Self::DeepSeek => "DeepSeek",
            Self::Qwen => "Qwen (Alibaba)",
            Self::Gemini => "Google Gemini",
            Self::Anthropic => "Anthropic",
            Self::OpenRouter => "OpenRouter",
            Self::AzureOpenAI => "Azure OpenAI",
            Self::MicroGpt => "MicroGPT (Local)",
            Self::Custom(name) => name.as_str(),
        }
    }
}

impl fmt::Display for ProviderBrand {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Custom(name) => write!(f, "custom:{name}"),
            other => {
                // Serialize to get the snake_case name
                let s = serde_json::to_string(other).unwrap_or_default();
                // Remove quotes
                write!(f, "{}", s.trim_matches('"'))
            }
        }
    }
}

// ============================================================================
// Provider Protocol
// ============================================================================

/// API protocol used by the provider.
///
/// This determines how HTTP requests are constructed and responses parsed.
/// Most providers (80%+) use OpenAI-compatible protocol.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProviderProtocol {
    /// OpenAI-compatible chat/completions API.
    ///
    /// Covers: Ollama, OpenAI, DeepSeek, Qwen, OpenRouter, Azure OpenAI.
    /// Uses `/v1/chat/completions` endpoint with SSE streaming.
    OpenAiCompat,

    /// Google Gemini native protocol.
    ///
    /// Uses `generateContent` / `streamGenerateContent` endpoints.
    /// Has unique features: `system_instruction`, inline image generation.
    Gemini,

    /// Anthropic Messages API.
    ///
    /// Uses `/v1/messages` endpoint.
    /// Has unique features: content blocks, extended thinking, different streaming events.
    Anthropic,

    /// Custom protocol (plugin-style, for future extensibility).
    Custom,
}

impl fmt::Display for ProviderProtocol {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OpenAiCompat => write!(f, "openai_compat"),
            Self::Gemini => write!(f, "gemini"),
            Self::Anthropic => write!(f, "anthropic"),
            Self::Custom => write!(f, "custom"),
        }
    }
}

// ============================================================================
// Provider Status
// ============================================================================

/// Provider lifecycle status.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProviderStatus {
    /// Provider is active and ready to serve requests.
    Active,
    /// Provider is temporarily disabled (manual).
    Disabled,
    /// Provider is experiencing issues (automatic, from health checks).
    Degraded,
}

impl Default for ProviderStatus {
    fn default() -> Self {
        Self::Active
    }
}

// ============================================================================
// Provider Capabilities
// ============================================================================

/// Capability declarations for a provider.
///
/// These flags indicate what features the provider supports.
/// Model-level capabilities can further refine these.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderCapabilities {
    /// Supports streaming responses (SSE / chunked transfer).
    #[serde(default = "default_true")]
    pub supports_streaming: bool,

    /// Supports function calling / tool use.
    #[serde(default)]
    pub supports_function_calling: bool,

    /// Supports image input (vision models).
    #[serde(default)]
    pub supports_vision: bool,

    /// Supports image generation output (e.g., Gemini, DALL-E).
    #[serde(default)]
    pub supports_image_generation: bool,

    /// Supports audio input (speech-to-text, audio understanding).
    #[serde(default)]
    pub supports_audio_input: bool,

    /// Supports audio output (text-to-speech).
    #[serde(default)]
    pub supports_audio_output: bool,

    /// Supports deep thinking / reasoning mode (e.g., DeepSeek R1, Claude thinking).
    #[serde(default)]
    pub supports_thinking: bool,

    /// Supports embedding generation.
    #[serde(default)]
    pub supports_embeddings: bool,

    /// Maximum context length (tokens). None = unknown / unlimited.
    #[serde(default)]
    pub max_context_length: Option<usize>,
}

impl Default for ProviderCapabilities {
    fn default() -> Self {
        Self {
            supports_streaming: true,
            supports_function_calling: false,
            supports_vision: false,
            supports_image_generation: false,
            supports_audio_input: false,
            supports_audio_output: false,
            supports_thinking: false,
            supports_embeddings: false,
            max_context_length: None,
        }
    }
}

impl ProviderCapabilities {
    /// Create capabilities with all features enabled (for testing).
    pub fn all() -> Self {
        Self {
            supports_streaming: true,
            supports_function_calling: true,
            supports_vision: true,
            supports_image_generation: true,
            supports_audio_input: true,
            supports_audio_output: true,
            supports_thinking: true,
            supports_embeddings: true,
            max_context_length: None,
        }
    }

    /// Returns default capabilities for a given brand.
    pub fn defaults_for_brand(brand: &ProviderBrand) -> Self {
        match brand {
            ProviderBrand::Ollama => Self {
                supports_streaming: true,
                supports_function_calling: true,
                supports_vision: true,
                supports_embeddings: true,
                ..Default::default()
            },
            ProviderBrand::OpenAI => Self {
                supports_streaming: true,
                supports_function_calling: true,
                supports_vision: true,
                supports_image_generation: true,
                supports_audio_input: true,
                supports_audio_output: true,
                supports_thinking: true,
                supports_embeddings: true,
                max_context_length: Some(128_000),
            },
            ProviderBrand::DeepSeek => Self {
                supports_streaming: true,
                supports_function_calling: true,
                supports_thinking: true,
                max_context_length: Some(64_000),
                ..Default::default()
            },
            ProviderBrand::Qwen => Self {
                supports_streaming: true,
                supports_function_calling: true,
                supports_vision: true,
                supports_image_generation: true,
                supports_audio_input: true,
                supports_audio_output: true,
                supports_thinking: true,
                supports_embeddings: true,
                max_context_length: Some(131_072),
            },
            ProviderBrand::Gemini => Self {
                supports_streaming: true,
                supports_function_calling: true,
                supports_vision: true,
                supports_image_generation: true,
                supports_audio_input: true,
                supports_audio_output: true,
                supports_thinking: true,
                supports_embeddings: true,
                max_context_length: Some(1_000_000),
            },
            ProviderBrand::Anthropic => Self {
                supports_streaming: true,
                supports_function_calling: true,
                supports_vision: true,
                supports_thinking: true,
                max_context_length: Some(200_000),
                ..Default::default()
            },
            ProviderBrand::OpenRouter => Self {
                supports_streaming: true,
                supports_function_calling: true,
                supports_vision: true,
                ..Default::default()
            },
            ProviderBrand::AzureOpenAI => Self {
                supports_streaming: true,
                supports_function_calling: true,
                supports_vision: true,
                supports_image_generation: true,
                supports_embeddings: true,
                max_context_length: Some(128_000),
                ..Default::default()
            },
            ProviderBrand::MicroGpt => Self {
                supports_streaming: true,
                ..Default::default()
            },
            ProviderBrand::Custom(_) => Self::default(),
        }
    }
}

// ============================================================================
// Rate Limit
// ============================================================================

/// Rate limit configuration for a provider.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimit {
    /// Maximum requests per minute.
    #[serde(default)]
    pub requests_per_minute: Option<u32>,

    /// Maximum tokens per minute.
    #[serde(default)]
    pub tokens_per_minute: Option<u64>,

    /// Maximum concurrent requests.
    #[serde(default)]
    pub max_concurrent: Option<u32>,
}

impl Default for RateLimit {
    fn default() -> Self {
        Self {
            requests_per_minute: None,
            tokens_per_minute: None,
            max_concurrent: None,
        }
    }
}

// ============================================================================
// LlmProvider
// ============================================================================

/// LLM Provider registration entity.
///
/// Represents a registered LLM service provider (e.g., a local Ollama instance,
/// a DeepSeek cloud account, or a custom OpenAI-compatible endpoint).
///
/// Providers are stored in the federation engine and can be dynamically
/// registered/unregistered at runtime without restart.
///
/// # Example
///
/// ```
/// use aifastdb_core::llm::{LlmProvider, ProviderBrand};
///
/// let provider = LlmProvider::new(
///     "ollama-local",
///     "My Local Ollama",
///     ProviderBrand::Ollama,
///     "http://localhost:11434",
/// );
/// assert_eq!(provider.brand, ProviderBrand::Ollama);
/// assert!(provider.is_healthy);
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmProvider {
    /// Unique provider ID (e.g., "ollama-local", "deepseek-cloud").
    pub id: String,

    /// Human-readable display name.
    pub name: String,

    /// Provider brand (determines default protocol and capabilities).
    pub brand: ProviderBrand,

    /// API base URL (e.g., "http://localhost:11434", "https://api.deepseek.com").
    pub base_url: String,

    /// API key (stored encrypted; None for local providers like Ollama).
    ///
    /// When serialized for API responses, this is masked (e.g., "sk-****abcd").
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub api_key: Option<String>,

    /// API version (used by Azure OpenAI, e.g., "2024-02-15-preview").
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub api_version: Option<String>,

    /// Organization ID (used by OpenAI).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub organization_id: Option<String>,

    /// API protocol to use for this provider.
    pub protocol: ProviderProtocol,

    /// Provider capability declarations.
    pub capabilities: ProviderCapabilities,

    /// Rate limit configuration.
    #[serde(default)]
    pub rate_limit: Option<RateLimit>,

    /// Routing priority (lower = higher priority for load balancing).
    #[serde(default = "default_priority")]
    pub priority: u32,

    /// Routing weight (for weighted round-robin; higher = more traffic).
    #[serde(default = "default_weight")]
    pub weight: u32,

    /// Whether the provider is currently healthy (auto-detected by HealthMonitor).
    #[serde(default = "default_true")]
    pub is_healthy: bool,

    /// Provider lifecycle status.
    #[serde(default)]
    pub status: ProviderStatus,

    /// Custom HTTP headers to include in requests.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub custom_headers: HashMap<String, String>,

    /// Connection timeout in seconds.
    #[serde(default = "default_timeout")]
    pub timeout_secs: u32,

    /// Arbitrary metadata.
    #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
    pub metadata: serde_json::Value,

    /// Creation timestamp (Unix milliseconds).
    pub created_at: i64,

    /// Last update timestamp (Unix milliseconds).
    pub updated_at: i64,
}

impl LlmProvider {
    /// Create a new provider with sensible defaults.
    pub fn new(
        id: impl Into<String>,
        name: impl Into<String>,
        brand: ProviderBrand,
        base_url: impl Into<String>,
    ) -> Self {
        let now = now_ms();
        let protocol = brand.default_protocol();
        let capabilities = ProviderCapabilities::defaults_for_brand(&brand);
        Self {
            id: id.into(),
            name: name.into(),
            brand,
            base_url: base_url.into(),
            api_key: None,
            api_version: None,
            organization_id: None,
            protocol,
            capabilities,
            rate_limit: None,
            priority: 100,
            weight: 1,
            is_healthy: true,
            status: ProviderStatus::Active,
            custom_headers: HashMap::new(),
            timeout_secs: 30,
            metadata: serde_json::Value::Null,
            created_at: now,
            updated_at: now,
        }
    }

    /// Builder: set API key.
    pub fn with_api_key(mut self, key: impl Into<String>) -> Self {
        self.api_key = Some(key.into());
        self
    }

    /// Builder: set API version (for Azure OpenAI).
    pub fn with_api_version(mut self, version: impl Into<String>) -> Self {
        self.api_version = Some(version.into());
        self
    }

    /// Builder: override protocol.
    pub fn with_protocol(mut self, protocol: ProviderProtocol) -> Self {
        self.protocol = protocol;
        self
    }

    /// Builder: set capabilities.
    pub fn with_capabilities(mut self, capabilities: ProviderCapabilities) -> Self {
        self.capabilities = capabilities;
        self
    }

    /// Builder: set priority (lower = higher priority).
    pub fn with_priority(mut self, priority: u32) -> Self {
        self.priority = priority;
        self
    }

    /// Builder: set rate limit.
    pub fn with_rate_limit(mut self, rate_limit: RateLimit) -> Self {
        self.rate_limit = Some(rate_limit);
        self
    }

    /// Builder: set timeout.
    pub fn with_timeout(mut self, timeout_secs: u32) -> Self {
        self.timeout_secs = timeout_secs;
        self
    }

    /// Returns the masked API key for display (e.g., "sk-****abcd").
    pub fn masked_api_key(&self) -> Option<String> {
        self.api_key.as_ref().map(|key| {
            if key.len() <= 8 {
                "****".to_string()
            } else {
                let suffix = &key[key.len() - 4..];
                format!("{}****{}", &key[..3], suffix)
            }
        })
    }

    /// Whether this provider needs an API key.
    pub fn requires_api_key(&self) -> bool {
        !matches!(self.brand, ProviderBrand::Ollama | ProviderBrand::MicroGpt)
    }

    /// Whether this provider is available (active + healthy).
    pub fn is_available(&self) -> bool {
        self.status == ProviderStatus::Active && self.is_healthy
    }
}

// ============================================================================
// Model Status
// ============================================================================

/// Model lifecycle status.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModelStatus {
    /// Model is active and available for use.
    Active,
    /// Model is deprecated (still works but will be removed).
    Deprecated,
    /// Model is disabled (not available for use).
    Disabled,
}

impl Default for ModelStatus {
    fn default() -> Self {
        Self::Active
    }
}

// ============================================================================
// Model Capabilities
// ============================================================================

/// Model-level capability declarations.
///
/// Inherits from `ProviderCapabilities` but can override per-model.
/// For example, a provider may support vision, but a specific text-only
/// model on that provider does not.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelCapabilities {
    /// Supports streaming responses.
    #[serde(default = "default_true")]
    pub supports_streaming: bool,

    /// Supports function calling / tool use.
    #[serde(default)]
    pub supports_function_calling: bool,

    /// Supports image input (vision).
    #[serde(default)]
    pub supports_vision: bool,

    /// Supports image generation output.
    #[serde(default)]
    pub supports_image_generation: bool,

    /// Supports audio input.
    #[serde(default)]
    pub supports_audio_input: bool,

    /// Supports audio output (TTS).
    #[serde(default)]
    pub supports_audio_output: bool,

    /// Supports deep thinking / reasoning mode.
    #[serde(default)]
    pub supports_thinking: bool,

    /// Supports embedding generation.
    #[serde(default)]
    pub supports_embeddings: bool,

    /// Maximum context length (tokens) for this specific model.
    #[serde(default)]
    pub max_context_length: Option<usize>,

    /// Maximum output tokens for this model.
    #[serde(default)]
    pub max_output_tokens: Option<usize>,
}

impl Default for ModelCapabilities {
    fn default() -> Self {
        Self {
            supports_streaming: true,
            supports_function_calling: false,
            supports_vision: false,
            supports_image_generation: false,
            supports_audio_input: false,
            supports_audio_output: false,
            supports_thinking: false,
            supports_embeddings: false,
            max_context_length: None,
            max_output_tokens: None,
        }
    }
}

impl From<&ProviderCapabilities> for ModelCapabilities {
    /// Inherit model capabilities from provider capabilities.
    fn from(pc: &ProviderCapabilities) -> Self {
        Self {
            supports_streaming: pc.supports_streaming,
            supports_function_calling: pc.supports_function_calling,
            supports_vision: pc.supports_vision,
            supports_image_generation: pc.supports_image_generation,
            supports_audio_input: pc.supports_audio_input,
            supports_audio_output: pc.supports_audio_output,
            supports_thinking: pc.supports_thinking,
            supports_embeddings: pc.supports_embeddings,
            max_context_length: pc.max_context_length,
            max_output_tokens: None,
        }
    }
}

// ============================================================================
// Model Pricing
// ============================================================================

/// Pricing information for a model.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelPricing {
    /// Cost per million input tokens.
    pub input_per_million_tokens: f64,

    /// Cost per million output tokens.
    pub output_per_million_tokens: f64,

    /// Currency code (e.g., "USD", "CNY").
    #[serde(default = "default_currency")]
    pub currency: String,
}

impl Default for ModelPricing {
    fn default() -> Self {
        Self {
            input_per_million_tokens: 0.0,
            output_per_million_tokens: 0.0,
            currency: "USD".to_string(),
        }
    }
}

impl ModelPricing {
    /// Calculate cost for a given token usage.
    pub fn calculate_cost(&self, input_tokens: u32, output_tokens: u32) -> f64 {
        let input_cost = (input_tokens as f64 / 1_000_000.0) * self.input_per_million_tokens;
        let output_cost = (output_tokens as f64 / 1_000_000.0) * self.output_per_million_tokens;
        input_cost + output_cost
    }
}

// ============================================================================
// Model Default Config
// ============================================================================

/// Default configuration for a model (overridable per-request).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelDefaultConfig {
    /// Default temperature (0.0 - 2.0).
    #[serde(default = "default_temperature")]
    pub temperature: f32,

    /// Default max output tokens.
    #[serde(default)]
    pub max_tokens: Option<usize>,

    /// Default top_p (nucleus sampling).
    #[serde(default)]
    pub top_p: Option<f32>,

    /// Default top_k sampling.
    #[serde(default)]
    pub top_k: Option<usize>,

    /// Default frequency penalty.
    #[serde(default)]
    pub frequency_penalty: Option<f32>,

    /// Default presence penalty.
    #[serde(default)]
    pub presence_penalty: Option<f32>,

    /// Default system prompt for this model.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub system_prompt: Option<String>,
}

impl Default for ModelDefaultConfig {
    fn default() -> Self {
        Self {
            temperature: 0.7,
            max_tokens: None,
            top_p: None,
            top_k: None,
            frequency_penalty: None,
            presence_penalty: None,
            system_prompt: None,
        }
    }
}

// ============================================================================
// LlmModel
// ============================================================================

/// LLM Model registration entity.
///
/// Each model belongs to a provider and has its own capabilities,
/// pricing, and default configuration.
///
/// # Example
///
/// ```
/// use aifastdb_core::llm::{LlmModel, ModelCapabilities};
///
/// let model = LlmModel::new(
///     "qwen2.5:14b",
///     "ollama-local",
///     "qwen2.5:14b",
///     "Qwen 2.5 14B",
/// );
/// assert!(!model.is_default);
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmModel {
    /// Unique model ID (e.g., "qwen2.5:14b", "gpt-4o").
    pub id: String,

    /// Associated provider ID.
    pub provider_id: String,

    /// Model name (as sent to the API, e.g., "qwen2.5:14b").
    pub name: String,

    /// Human-readable display name.
    pub display_name: String,

    /// Model family (e.g., "qwen2.5", "gpt-4", "claude-3.5").
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub model_family: Option<String>,

    /// Model capabilities (inherits from provider, can override).
    pub capabilities: ModelCapabilities,

    /// Default configuration for this model.
    #[serde(default)]
    pub default_config: ModelDefaultConfig,

    /// Pricing information.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pricing: Option<ModelPricing>,

    /// Whether this is the default model for new conversations.
    #[serde(default)]
    pub is_default: bool,

    /// Model lifecycle status.
    #[serde(default)]
    pub status: ModelStatus,

    /// Arbitrary metadata.
    #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
    pub metadata: serde_json::Value,

    /// Creation timestamp (Unix milliseconds).
    pub created_at: i64,

    /// Last update timestamp (Unix milliseconds).
    pub updated_at: i64,
}

impl LlmModel {
    /// Create a new model with sensible defaults.
    pub fn new(
        id: impl Into<String>,
        provider_id: impl Into<String>,
        name: impl Into<String>,
        display_name: impl Into<String>,
    ) -> Self {
        let now = now_ms();
        Self {
            id: id.into(),
            provider_id: provider_id.into(),
            name: name.into(),
            display_name: display_name.into(),
            model_family: None,
            capabilities: ModelCapabilities::default(),
            default_config: ModelDefaultConfig::default(),
            pricing: None,
            is_default: false,
            status: ModelStatus::Active,
            metadata: serde_json::Value::Null,
            created_at: now,
            updated_at: now,
        }
    }

    /// Builder: set model family.
    pub fn with_family(mut self, family: impl Into<String>) -> Self {
        self.model_family = Some(family.into());
        self
    }

    /// Builder: set capabilities.
    pub fn with_capabilities(mut self, capabilities: ModelCapabilities) -> Self {
        self.capabilities = capabilities;
        self
    }

    /// Builder: set pricing.
    pub fn with_pricing(mut self, pricing: ModelPricing) -> Self {
        self.pricing = Some(pricing);
        self
    }

    /// Builder: set as default model.
    pub fn as_default(mut self) -> Self {
        self.is_default = true;
        self
    }

    /// Builder: set default config.
    pub fn with_default_config(mut self, config: ModelDefaultConfig) -> Self {
        self.default_config = config;
        self
    }

    /// Whether this model is available (active status).
    pub fn is_available(&self) -> bool {
        self.status == ModelStatus::Active
    }
}

// ============================================================================
// ChatConfig (per-request)
// ============================================================================

/// Per-request chat configuration.
///
/// Overrides model defaults for a specific request.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatConfig {
    /// Temperature (0.0 - 2.0). None = use model default.
    #[serde(default)]
    pub temperature: Option<f32>,

    /// Maximum output tokens. None = use model default.
    #[serde(default)]
    pub max_tokens: Option<usize>,

    /// Top-p nucleus sampling. None = use model default.
    #[serde(default)]
    pub top_p: Option<f32>,

    /// Top-k sampling. None = use model default.
    #[serde(default)]
    pub top_k: Option<usize>,

    /// Frequency penalty. None = use model default.
    #[serde(default)]
    pub frequency_penalty: Option<f32>,

    /// Presence penalty. None = use model default.
    #[serde(default)]
    pub presence_penalty: Option<f32>,

    /// Whether to enable streaming.
    #[serde(default = "default_true")]
    pub stream: bool,

    /// Whether to enable thinking/reasoning mode.
    #[serde(default)]
    pub enable_thinking: bool,

    /// Tool/function definitions for function calling.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tools: Vec<ToolDefinition>,

    /// Stop sequences.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub stop: Vec<String>,

    /// Response format (e.g., "json_object").
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub response_format: Option<String>,

    /// Seed for deterministic generation.
    #[serde(default)]
    pub seed: Option<u64>,
}

impl Default for ChatConfig {
    fn default() -> Self {
        Self {
            temperature: None,
            max_tokens: None,
            top_p: None,
            top_k: None,
            frequency_penalty: None,
            presence_penalty: None,
            stream: true,
            enable_thinking: false,
            tools: Vec::new(),
            stop: Vec::new(),
            response_format: None,
            seed: None,
        }
    }
}

// ============================================================================
// Tool Definition (for Function Calling)
// ============================================================================

/// Tool/function definition for function calling.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
    /// Tool type (always "function" for now).
    #[serde(rename = "type", default = "default_tool_type")]
    pub tool_type: String,

    /// Function definition.
    pub function: FunctionDefinition,
}

/// Function definition within a tool.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionDefinition {
    /// Function name.
    pub name: String,

    /// Function description.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,

    /// JSON Schema for function parameters.
    #[serde(default)]
    pub parameters: serde_json::Value,
}

// ============================================================================
// Token Usage
// ============================================================================

/// Token usage information for a single response.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenUsage {
    /// Number of prompt (input) tokens.
    pub prompt_tokens: u32,

    /// Number of completion (output) tokens.
    pub completion_tokens: u32,

    /// Total tokens (prompt + completion).
    pub total_tokens: u32,

    /// Cost of this request (calculated from model pricing).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cost: Option<f64>,

    /// Reasoning/thinking tokens (for deep reasoning models).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reasoning_tokens: Option<u32>,

    /// Cached/read tokens (for providers that report cache hits).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cached_tokens: Option<u32>,
}

impl TokenUsage {
    /// Create from basic counts.
    pub fn new(prompt_tokens: u32, completion_tokens: u32) -> Self {
        Self {
            prompt_tokens,
            completion_tokens,
            total_tokens: prompt_tokens + completion_tokens,
            ..Default::default()
        }
    }
}

// ============================================================================
// Finish Reason
// ============================================================================

/// Reason why the model stopped generating.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
    /// Natural stop (end of response).
    Stop,
    /// Reached max tokens limit.
    Length,
    /// Model wants to call a tool/function.
    ToolCalls,
    /// Content was filtered by safety.
    ContentFilter,
    /// Generation was cancelled by user.
    Cancelled,
    /// Unknown/other reason.
    Other(String),
}

impl Default for FinishReason {
    fn default() -> Self {
        Self::Stop
    }
}

// ============================================================================
// ChatResponse (non-streaming)
// ============================================================================

/// Non-streaming chat response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatResponse {
    /// Generated text content.
    pub content: String,

    /// Thinking/reasoning content (if enabled).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub thinking: Option<String>,

    /// Tool calls requested by the model.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tool_calls: Vec<ToolCall>,

    /// Token usage information.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub usage: Option<TokenUsage>,

    /// Why generation stopped.
    pub finish_reason: FinishReason,

    /// The model that actually generated the response.
    pub model: String,

    /// Response generation time in milliseconds.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<u64>,
}

/// A tool/function call requested by the model.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
    /// Tool call ID (for matching results).
    pub id: String,

    /// Function name.
    pub name: String,

    /// Function arguments (JSON string).
    pub arguments: String,
}

// ============================================================================
// StreamEvent (unified streaming events)
// ============================================================================

/// Unified streaming event types.
///
/// These events cover all possible streaming outputs across all providers
/// (OpenAI, Gemini, Anthropic, etc.) mapped to a single enum.
///
/// # SSE mapping
///
/// Each variant maps to an SSE event type:
/// - `TextDelta` → `event: text_delta`
/// - `ThinkingDelta` → `event: thinking_delta`
/// - `AudioDelta` → `event: audio_delta`
/// - `ImageGenerated` → `event: image_generated`
/// - `ToolCallDelta` → `event: tool_call`
/// - `ToolResult` → `event: tool_result`
/// - `Usage` → `event: usage`
/// - `RagContext` → `event: rag_context`
/// - `Done` → `event: done`
/// - `Error` → `event: error`
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum StreamEvent {
    /// Text content fragment.
    TextDelta {
        content: String,
    },

    /// Thinking/reasoning content fragment.
    ThinkingDelta {
        content: String,
    },

    /// Audio data fragment (base64 encoded).
    AudioDelta {
        data: String,
        format: AudioFormat,
        sample_rate: u32,
    },

    /// Image generated by the model.
    ImageGenerated {
        url: String,
        mime_type: String,
    },

    /// Tool/function call (incremental).
    ToolCallDelta {
        id: String,
        name: String,
        arguments_delta: String,
    },

    /// Tool execution result (sent back to model).
    ToolResult {
        call_id: String,
        result: String,
    },

    /// Token usage information.
    Usage(TokenUsage),

    /// RAG context sources (sent before generation).
    RagContext {
        sources: Vec<serde_json::Value>,
        relevance_scores: Vec<f32>,
    },

    /// Agent: planning step (Phase-87).
    AgentPlan {
        plan: String,
        step_count: usize,
    },

    /// Agent: thought/reasoning step (Phase-87).
    AgentThought {
        step: usize,
        content: String,
    },

    /// Agent: tool action step (Phase-87).
    AgentAction {
        step: usize,
        tool_name: String,
        tool_args: String,
    },

    /// Agent: observation of tool result (Phase-87).
    AgentObservation {
        step: usize,
        tool_name: String,
        success: bool,
        output: String,
    },

    /// Generation complete.
    Done {
        message_id: String,
        finish_reason: FinishReason,
    },

    /// Error during generation.
    Error {
        code: String,
        message: String,
    },
}

// ============================================================================
// Audio Format
// ============================================================================

/// Audio format enumeration.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AudioFormat {
    Pcm16,
    Mp3,
    Opus,
    Wav,
    Flac,
}

impl Default for AudioFormat {
    fn default() -> Self {
        Self::Pcm16
    }
}

// ============================================================================
// Helper functions
// ============================================================================

fn now_ms() -> i64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as i64
}

fn default_true() -> bool {
    true
}

fn default_priority() -> u32 {
    100
}

fn default_weight() -> u32 {
    1
}

fn default_timeout() -> u32 {
    30
}

fn default_temperature() -> f32 {
    0.7
}

fn default_currency() -> String {
    "USD".to_string()
}

fn default_tool_type() -> String {
    "function".to_string()
}

// ============================================================================
// Tests
// ============================================================================

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

    #[test]
    fn test_provider_brand_display() {
        assert_eq!(ProviderBrand::Ollama.display_name(), "Ollama");
        assert_eq!(ProviderBrand::DeepSeek.display_name(), "DeepSeek");
        assert_eq!(
            ProviderBrand::Custom("MyProvider".into()).display_name(),
            "MyProvider"
        );
    }

    #[test]
    fn test_provider_brand_default_protocol() {
        assert_eq!(
            ProviderBrand::Ollama.default_protocol(),
            ProviderProtocol::OpenAiCompat
        );
        assert_eq!(
            ProviderBrand::OpenAI.default_protocol(),
            ProviderProtocol::OpenAiCompat
        );
        assert_eq!(
            ProviderBrand::Gemini.default_protocol(),
            ProviderProtocol::Gemini
        );
        assert_eq!(
            ProviderBrand::Anthropic.default_protocol(),
            ProviderProtocol::Anthropic
        );
    }

    #[test]
    fn test_provider_new() {
        let p = LlmProvider::new(
            "test-ollama",
            "Test Ollama",
            ProviderBrand::Ollama,
            "http://localhost:11434",
        );
        assert_eq!(p.id, "test-ollama");
        assert_eq!(p.protocol, ProviderProtocol::OpenAiCompat);
        assert!(p.is_healthy);
        assert!(p.is_available());
        assert!(!p.requires_api_key());
    }

    #[test]
    fn test_provider_with_api_key() {
        let p = LlmProvider::new(
            "deepseek",
            "DeepSeek",
            ProviderBrand::DeepSeek,
            "https://api.deepseek.com",
        )
        .with_api_key("sk-abc123456789xyz");

        assert!(p.requires_api_key());
        let masked = p.masked_api_key().unwrap();
        assert!(masked.contains("****"));
        assert!(masked.ends_with("xyz"));
    }

    #[test]
    fn test_model_new() {
        let m = LlmModel::new(
            "qwen2.5:14b",
            "ollama-local",
            "qwen2.5:14b",
            "Qwen 2.5 14B",
        )
        .with_family("qwen2.5")
        .as_default();

        assert_eq!(m.id, "qwen2.5:14b");
        assert!(m.is_default);
        assert!(m.is_available());
        assert_eq!(m.model_family.as_deref(), Some("qwen2.5"));
    }

    #[test]
    fn test_model_pricing() {
        let pricing = ModelPricing {
            input_per_million_tokens: 0.15,
            output_per_million_tokens: 0.60,
            currency: "USD".to_string(),
        };
        // 1000 input tokens + 500 output tokens
        let cost = pricing.calculate_cost(1000, 500);
        assert!((cost - 0.00045).abs() < 1e-10);
    }

    #[test]
    fn test_token_usage() {
        let usage = TokenUsage::new(100, 50);
        assert_eq!(usage.total_tokens, 150);
    }

    #[test]
    fn test_capabilities_defaults_for_brand() {
        let caps = ProviderCapabilities::defaults_for_brand(&ProviderBrand::OpenAI);
        assert!(caps.supports_streaming);
        assert!(caps.supports_function_calling);
        assert!(caps.supports_vision);
        assert_eq!(caps.max_context_length, Some(128_000));

        let caps = ProviderCapabilities::defaults_for_brand(&ProviderBrand::MicroGpt);
        assert!(caps.supports_streaming);
        assert!(!caps.supports_function_calling);
    }

    #[test]
    fn test_provider_serde_roundtrip() {
        let p = LlmProvider::new(
            "test",
            "Test",
            ProviderBrand::Custom("myapi".into()),
            "http://localhost:8080",
        )
        .with_api_key("secret");

        let json = serde_json::to_string(&p).unwrap();
        let p2: LlmProvider = serde_json::from_str(&json).unwrap();
        assert_eq!(p2.id, "test");
        assert_eq!(p2.brand, ProviderBrand::Custom("myapi".into()));
        assert_eq!(p2.api_key, Some("secret".to_string()));
    }

    #[test]
    fn test_stream_event_serde() {
        let event = StreamEvent::TextDelta {
            content: "Hello".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        assert!(json.contains("text_delta"));
        assert!(json.contains("Hello"));

        let done = StreamEvent::Done {
            message_id: "msg-123".into(),
            finish_reason: FinishReason::Stop,
        };
        let json = serde_json::to_string(&done).unwrap();
        assert!(json.contains("done"));
        assert!(json.contains("msg-123"));
    }

    #[test]
    fn test_chat_config_defaults() {
        let config = ChatConfig::default();
        assert!(config.stream);
        assert!(!config.enable_thinking);
        assert!(config.temperature.is_none());
        assert!(config.tools.is_empty());
    }
}
