//! Type definitions for gateway configuration.
//!
//! These types mirror the structure of config.yml and provide strongly-typed
//! access to model configurations.

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

/// Root configuration structure containing all model definitions.
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct GatewayConfig {
    /// Map of model IDs to their configurations.
    /// Model IDs follow the format: `{provider}:{modality}:{model-name}`
    #[serde(default)]
    pub models: HashMap<String, ModelConfig>,
}

/// Configuration for a single model.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ModelConfig {
    /// Human-readable display name for the model.
    pub display_name: String,

    /// The backend provider name (e.g., "azure-foundry", "openrouter").
    pub provider_name: String,

    /// Input modalities this model accepts.
    pub input_modality: Vec<Modality>,

    /// Output modalities this model produces.
    pub output_modality: Vec<Modality>,

    /// Runtime configuration for API calls.
    pub runtime: RuntimeConfig,

    /// Authentication configuration.
    pub auth: AuthConfig,

    /// Secrets configuration for API keys.
    pub secrets: SecretsConfig,

    /// Model capabilities.
    pub capabilities: CapabilitiesConfig,

    /// Governance and approval information.
    pub governance: GovernanceConfig,

    /// Cost and pricing information.
    pub cost: CostConfig,

    /// Optional fallback model IDs if this model fails.
    #[serde(default)]
    pub fallbacks: Vec<String>,
}

/// Modality types for input/output.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum Modality {
    Text,
    Image,
    Audio,
    Embedding,
    Video,
}

impl std::fmt::Display for Modality {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Modality::Text => write!(f, "text"),
            Modality::Image => write!(f, "image"),
            Modality::Audio => write!(f, "audio"),
            Modality::Embedding => write!(f, "embedding"),
            Modality::Video => write!(f, "video"),
        }
    }
}

/// Runtime configuration for API calls.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RuntimeConfig {
    /// Base URL for the API (without path).
    pub base_url: String,

    /// Full URL for API calls (with path and query parameters).
    pub full_url: String,

    /// Communication protocol.
    pub protocol: Protocol,

    /// API version string.
    pub api_version: String,

    /// Whether to use max_completion_tokens instead of max_tokens.
    #[serde(default)]
    pub use_max_completion_tokens: bool,

    /// Whether the model supports temperature parameter.
    #[serde(default = "default_true")]
    pub has_temperature_supported: bool,

    /// Additional notes about the model (e.g., "requires_model_param_in_request_body").
    #[serde(default)]
    pub additional_notes: Vec<String>,

    /// Whether to include model parameter in request body (some Azure endpoints require this).
    #[serde(default)]
    pub model_param: bool,

    /// Request timeout in seconds (optional, defaults to 60).
    #[serde(default = "default_timeout")]
    pub timeout_seconds: u64,

    /// Default max tokens if not specified in request (optional).
    #[serde(default = "default_max_tokens")]
    pub max_tokens_default: u32,

    /// Default temperature if not specified in request (optional).
    #[serde(default = "default_temperature")]
    pub temperature_default: f32,
}

fn default_true() -> bool {
    true
}

fn default_timeout() -> u64 {
    60
}

fn default_max_tokens() -> u32 {
    4096
}

fn default_temperature() -> f32 {
    0.7
}

/// Protocol types for API communication.
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Protocol {
    /// OpenAI-compatible HTTP API (chat completions format).
    #[serde(rename = "openai_compatible")]
    OpenAICompatible,
    /// Azure SDK protocol.
    AzureSdk,
    /// OpenRouter SDK protocol.
    OpenrouterSdk,
    /// Anthropic SDK protocol (Messages API).
    AnthropicSdk,
    /// Vertex AI protocol (not implemented).
    VertexAi,
    /// Azure Cognitive Speech Services (STT/TTS) via REST.
    #[serde(rename = "azure_speech")]
    AzureSpeech,
    /// Azure Cognitive Speech Services via Rust SDK (WebSocket; use for Telegram etc.).
    #[serde(rename = "azure_speech_sdk")]
    AzureSpeechSdk,
}

/// Authentication configuration.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuthConfig {
    /// Authentication type.
    #[serde(rename = "type")]
    pub auth_type: AuthType,
}

/// Authentication types.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AuthType {
    /// Bearer token in Authorization header.
    Bearer,
    /// API key in custom header (e.g., api-key).
    Header,
}

/// Secrets configuration for API key resolution.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SecretsConfig {
    /// Secret storage type.
    #[serde(rename = "type")]
    pub secret_type: SecretType,

    /// Environment variable name or secret key.
    pub name: String,
}

/// Secret storage types.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SecretType {
    /// Secret stored in environment variable.
    Environment,
}

/// Model capabilities configuration.
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct CapabilitiesConfig {
    /// Whether the model supports tool/function calling.
    #[serde(default)]
    pub tool_calls: bool,

    /// Whether the model supports reasoning/thinking capabilities.
    #[serde(default)]
    pub reasoning: bool,
}

/// Governance and approval configuration.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GovernanceConfig {
    /// Whether model usage requires approval.
    #[serde(default)]
    pub is_use_approve: bool,

    /// Who approved the model usage.
    pub approved_by: Option<String>,

    /// When the model was approved (ISO 8601 timestamp).
    pub approval_date: Option<String>,

    /// Lifecycle status of the model.
    pub lifecycle_status: LifecycleStatus,
}

/// Lifecycle status of a model.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum LifecycleStatus {
    /// Model is active and available for use.
    Active,
    /// Model is deprecated but still functional.
    Deprecated,
    /// Model is disabled and should not be used.
    Disabled,
}

/// Cost and pricing configuration.
///
/// All token costs are per 1K tokens (USD). Image cost is per 1K pixels (USD).
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct CostConfig {
    /// Pricing provider (e.g., "portkey", "openrouter").
    pub provider: Option<String>,

    /// Key identifier in the pricing provider's system.
    pub provider_key: Option<String>,

    /// Currency for pricing (e.g., "USD").
    pub currency: Option<String>,

    /// Cost per 1K input tokens (USD).
    pub cost_per_1m_input: Option<f64>,

    /// Cost per 1K output tokens (USD).
    pub cost_per_1m_output: Option<f64>,

    /// Cost per 1K cached input tokens (USD).
    pub cost_per_1m_cached_input: Option<f64>,

    /// Cost per 1K cached output tokens (USD).
    pub cost_per_1m_cached_output: Option<f64>,

    /// Cost per 1K pixels for image generation/usage (USD). Used for image modality models.
    pub cost_per_1m_pixels: Option<f64>,
}

impl GatewayConfig {
    /// Get a model configuration by ID.
    pub fn get_model(&self, model_id: &str) -> Option<&ModelConfig> {
        self.models.get(model_id)
    }

    /// List all model IDs.
    pub fn list_models(&self) -> Vec<&String> {
        self.models.keys().collect()
    }

    /// Count total models.
    pub fn model_count(&self) -> usize {
        self.models.len()
    }
}

impl ModelConfig {
    /// Check if this model supports a given input modality.
    pub fn supports_input_modality(&self, modality: &Modality) -> bool {
        self.input_modality.contains(modality)
    }

    /// Check if this model supports a given output modality.
    pub fn supports_output_modality(&self, modality: &Modality) -> bool {
        self.output_modality.contains(modality)
    }

    /// Check if this model supports tool calling.
    pub fn supports_tools(&self) -> bool {
        self.capabilities.tool_calls
    }

    /// Check if this model supports reasoning.
    pub fn supports_reasoning(&self) -> bool {
        self.capabilities.reasoning
    }

    /// Check if this model requires the model param in request body.
    pub fn requires_model_in_body(&self) -> bool {
        // Check direct model_param flag first, then fall back to additional_notes
        self.runtime.model_param
            || self
                .runtime
                .additional_notes
                .iter()
                .any(|note| note == "requires_model_param_in_request_body")
    }

    /// Get the environment variable name for the API key.
    pub fn api_key_env_var(&self) -> &str {
        &self.secrets.name
    }
}

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

    #[test]
    fn test_modality_display() {
        assert_eq!(format!("{}", Modality::Text), "text");
        assert_eq!(format!("{}", Modality::Image), "image");
        assert_eq!(format!("{}", Modality::Audio), "audio");
        assert_eq!(format!("{}", Modality::Embedding), "embedding");
        assert_eq!(format!("{}", Modality::Video), "video");
    }

    #[test]
    fn test_default_values() {
        assert!(default_true());
        assert_eq!(default_timeout(), 60);
        assert_eq!(default_max_tokens(), 4096);
        assert!((default_temperature() - 0.7).abs() < f32::EPSILON);
    }
}
