use serde::{Deserialize, Serialize};

/// Supported LLM provider backends.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LlmProvider {
    /// LiteLLM proxy for unified model access
    LiteLlm,
    /// Hugging Face Inference API
    HuggingFace,
    /// OpenAI-compatible endpoints
    OpenAi,
    /// Anthropic Claude API
    Anthropic,
}

impl LlmProvider {
    /// Returns the display name for a provider.
    pub fn label(&self) -> &'static str {
        match self {
            Self::LiteLlm => "LiteLLM",
            Self::HuggingFace => "Hugging Face",
            Self::OpenAi => "OpenAI",
            Self::Anthropic => "Anthropic",
        }
    }
}

/// A model available from a provider.
///
/// # Fields
///
/// * `id` - Unique model identifier (e.g. "qwen3-32b")
/// * `name` - Human-readable display name
/// * `provider` - Which provider hosts this model
/// * `context_window` - Maximum context length in tokens
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ModelEntry {
    pub id: String,
    pub name: String,
    pub provider: LlmProvider,
    pub context_window: u32,
}

/// An embedding model available from a provider.
///
/// # Fields
///
/// * `id` - Unique embedding model identifier
/// * `name` - Human-readable display name
/// * `provider` - Which provider hosts this model
/// * `dimensions` - Output embedding dimensions
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EmbeddingEntry {
    pub id: String,
    pub name: String,
    pub provider: LlmProvider,
    pub dimensions: u32,
}

/// Active provider configuration state.
///
/// # Fields
///
/// * `provider` - Currently selected provider
/// * `selected_model` - ID of the active chat model
/// * `selected_embedding` - ID of the active embedding model
/// * `api_key_set` - Whether an API key has been configured
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProviderConfig {
    pub provider: LlmProvider,
    pub selected_model: String,
    pub selected_embedding: String,
    pub api_key_set: bool,
}

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

    #[test]
    fn llm_provider_label_litellm() {
        assert_eq!(LlmProvider::LiteLlm.label(), "LiteLLM");
    }

    #[test]
    fn llm_provider_label_hugging_face() {
        assert_eq!(LlmProvider::HuggingFace.label(), "Hugging Face");
    }

    #[test]
    fn llm_provider_label_openai() {
        assert_eq!(LlmProvider::OpenAi.label(), "OpenAI");
    }

    #[test]
    fn llm_provider_label_anthropic() {
        assert_eq!(LlmProvider::Anthropic.label(), "Anthropic");
    }

    #[test]
    fn llm_provider_serde_round_trip() {
        for variant in [
            LlmProvider::LiteLlm,
            LlmProvider::HuggingFace,
            LlmProvider::OpenAi,
            LlmProvider::Anthropic,
        ] {
            let json = serde_json::to_string(&variant)
                .unwrap_or_else(|_| panic!("serialize {:?}", variant));
            let back: LlmProvider =
                serde_json::from_str(&json).unwrap_or_else(|_| panic!("deserialize {:?}", variant));
            assert_eq!(variant, back);
        }
    }

    #[test]
    fn model_entry_serde_round_trip() {
        let entry = ModelEntry {
            id: "qwen3-32b".into(),
            name: "Qwen3 32B".into(),
            provider: LlmProvider::LiteLlm,
            context_window: 32,
        };
        let json = serde_json::to_string(&entry).expect("serialize ModelEntry");
        let back: ModelEntry = serde_json::from_str(&json).expect("deserialize ModelEntry");
        assert_eq!(entry, back);
    }

    #[test]
    fn embedding_entry_serde_round_trip() {
        let entry = EmbeddingEntry {
            id: "nomic-embed".into(),
            name: "Nomic Embed".into(),
            provider: LlmProvider::HuggingFace,
            dimensions: 768,
        };
        let json = serde_json::to_string(&entry).expect("serialize EmbeddingEntry");
        let back: EmbeddingEntry = serde_json::from_str(&json).expect("deserialize EmbeddingEntry");
        assert_eq!(entry, back);
    }

    #[test]
    fn provider_config_serde_round_trip() {
        let cfg = ProviderConfig {
            provider: LlmProvider::Anthropic,
            selected_model: "claude-3".into(),
            selected_embedding: "embed-v1".into(),
            api_key_set: true,
        };
        let json = serde_json::to_string(&cfg).expect("serialize ProviderConfig");
        let back: ProviderConfig = serde_json::from_str(&json).expect("deserialize ProviderConfig");
        assert_eq!(cfg, back);
    }
}
