//! Telnyx AI inference model_provider.

use crate::traits::{ChatMessage, ModelProvider};
use async_trait::async_trait;
use reqwest::Client;
use serde::Deserialize;

/// Telnyx Inference Engine public endpoint.
pub(crate) const BASE_URL: &str = "https://api.telnyx.com/v2/ai";

pub struct TelnyxModelProvider {
    /// `[providers.models.telnyx.<alias>]` config-key alias.
    alias: String,
    /// Telnyx API key
    api_key: Option<String>,
    /// HTTP client for API requests
    client: Client,
}

/// Typed builder for [`TelnyxModelProvider`].
///
/// Only `alias` is required; `api_key` falls back to the
/// `TELNYX_API_KEY` environment variable.
#[must_use]
pub struct TelnyxBuilder {
    alias: String,
    api_key: Option<String>,
}

impl TelnyxBuilder {
    /// Set an explicit Telnyx API key. When unset, `build()` reads
    /// `TELNYX_API_KEY` from the environment via `resolve_telnyx_api_key`.
    pub fn api_key(mut self, api_key: Option<&str>) -> Self {
        self.api_key = api_key.map(str::to_string);
        self
    }

    pub fn build(self) -> TelnyxModelProvider {
        TelnyxModelProvider {
            alias: self.alias,
            api_key: resolve_telnyx_api_key(self.api_key.as_deref()),
            client: Client::builder()
                .timeout(std::time::Duration::from_secs(120))
                .connect_timeout(std::time::Duration::from_secs(10))
                .build()
                .unwrap_or_else(|_| Client::new()),
        }
    }
}

impl TelnyxModelProvider {
    /// Entry point. Only `alias` is required; every other field is set
    /// via a labelled chain method on the returned [`TelnyxBuilder`].
    pub fn builder(alias: &str) -> TelnyxBuilder {
        TelnyxBuilder {
            alias: alias.to_string(),
            api_key: None,
        }
    }

    /// List available models from Telnyx AI.
    /// Returns a list of model IDs that can be used with the chat API.
    pub async fn list_models(&self) -> anyhow::Result<Vec<String>> {
        let api_key = self.api_key.as_ref().ok_or_else(|| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({"missing": "api_key"})),
                "telnyx: API key not configured"
            );
            anyhow::Error::msg("Telnyx API key not set. Set TELNYX_API_KEY environment variable.")
        })?;

        let response = self
            .client
            .get(format!("{}/models", BASE_URL))
            .header("Authorization", format!("Bearer {}", api_key))
            .send()
            .await?;

        if !response.status().is_success() {
            let error = response.text().await?;
            anyhow::bail!("Failed to list Telnyx models: {}", error);
        }

        let models_response: ModelsResponse = response.json().await?;
        Ok(models_response.data.into_iter().map(|m| m.id).collect())
    }

    /// Build the chat completions URL
    fn chat_url(&self) -> String {
        format!("{}/chat/completions", BASE_URL)
    }
}

fn resolve_telnyx_api_key(api_key: Option<&str>) -> Option<String> {
    api_key
        .map(str::trim)
        .filter(|k| !k.is_empty())
        .map(ToString::to_string)
}

/// Response from the /models endpoint
#[derive(Debug, Deserialize)]
struct ModelsResponse {
    data: Vec<ModelInfo>,
}

#[derive(Debug, Deserialize)]
struct ModelInfo {
    id: String,
}

/// Request body for chat completions
#[derive(Debug, serde::Serialize)]
struct ChatRequest {
    model: String,
    messages: Vec<Message>,
    #[serde(skip_serializing_if = "Option::is_none")]
    temperature: Option<f64>,
}

#[derive(Debug, serde::Serialize)]
struct Message {
    role: String,
    content: String,
}

/// Response from chat completions API
#[derive(Debug, Deserialize)]
struct ChatResponse {
    choices: Vec<Choice>,
}

#[derive(Debug, Deserialize)]
struct Choice {
    message: ResponseMessage,
}

#[derive(Debug, Deserialize)]
struct ResponseMessage {
    content: String,
}

#[async_trait]
impl ModelProvider for TelnyxModelProvider {
    // ── ModelProvider-family defaults ──
    fn default_base_url(&self) -> Option<&str> {
        Some(BASE_URL)
    }

    async fn chat_with_system(
        &self,
        system_prompt: Option<&str>,
        message: &str,
        model: &str,
        temperature: Option<f64>,
    ) -> anyhow::Result<String> {
        let api_key = self.api_key.as_ref().ok_or_else(|| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({"missing": "api_key"})),
                "telnyx: API key not configured"
            );
            anyhow::Error::msg(
                "Telnyx API key not set. Set TELNYX_API_KEY environment variable or run `zeroclaw quickstart --model-provider telnyx --api-key <key>`.",
            )
        })?;

        let mut messages = Vec::new();

        if let Some(sys) = system_prompt {
            messages.push(Message {
                role: "system".to_string(),
                content: sys.to_string(),
            });
        }

        messages.push(Message {
            role: "user".to_string(),
            content: message.to_string(),
        });

        let request = ChatRequest {
            model: model.to_string(),
            messages,
            temperature,
        };

        let response = self
            .client
            .post(self.chat_url())
            .header("Authorization", format!("Bearer {}", api_key))
            .header("Content-Type", "application/json")
            .json(&request)
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status();
            let error = response.text().await?;
            let sanitized = super::sanitize_api_error(&error);
            anyhow::bail!("Telnyx API error ({}): {}", status, sanitized);
        }

        let chat_response: ChatResponse = response.json().await?;

        chat_response
            .choices
            .into_iter()
            .next()
            .map(|c| c.message.content)
            .ok_or_else(|| {
                ::zeroclaw_log::record!(
                    ERROR,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure),
                    "telnyx: empty choices in response"
                );
                anyhow::Error::msg("No response from Telnyx")
            })
    }

    async fn chat_with_history(
        &self,
        messages: &[ChatMessage],
        model: &str,
        temperature: Option<f64>,
    ) -> anyhow::Result<String> {
        let api_key = self.api_key.as_ref().ok_or_else(|| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({"missing": "api_key"})),
                "telnyx: API key not configured"
            );
            anyhow::Error::msg(
                "Telnyx API key not set. Set TELNYX_API_KEY environment variable or run `zeroclaw quickstart --model-provider telnyx --api-key <key>`.",
            )
        })?;

        let api_messages: Vec<Message> = messages
            .iter()
            .map(|m| Message {
                role: m.role.clone(),
                content: m.content.clone(),
            })
            .collect();

        let request = ChatRequest {
            model: model.to_string(),
            messages: api_messages,
            temperature,
        };

        let response = self
            .client
            .post(self.chat_url())
            .header("Authorization", format!("Bearer {}", api_key))
            .header("Content-Type", "application/json")
            .json(&request)
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status();
            let error = response.text().await?;
            let sanitized = super::sanitize_api_error(&error);
            anyhow::bail!("Telnyx API error ({}): {}", status, sanitized);
        }

        let chat_response: ChatResponse = response.json().await?;

        chat_response
            .choices
            .into_iter()
            .next()
            .map(|c| c.message.content)
            .ok_or_else(|| {
                ::zeroclaw_log::record!(
                    ERROR,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure),
                    "telnyx: empty choices in response"
                );
                anyhow::Error::msg("No response from Telnyx")
            })
    }

    async fn warmup(&self) -> anyhow::Result<()> {
        // Pre-warm the connection pool
        let _ = self.client.get(format!("{}/models", BASE_URL)).send().await;
        Ok(())
    }
}

/// Popular Telnyx AI models for easy reference.
pub mod models {
    /// OpenAI GPT-4o (recommended for most tasks)
    pub const GPT_4O: &str = "openai/gpt-4o";
    /// OpenAI GPT-4o Mini (fast and cost-effective)
    pub const GPT_4O_MINI: &str = "openai/gpt-4o-mini";
    /// OpenAI GPT-4 Turbo
    pub const GPT_4_TURBO: &str = "openai/gpt-4-turbo";
    /// Anthropic Claude 3.5 Sonnet (via Telnyx proxy)
    pub const CLAUDE_3_5_SONNET: &str = "anthropic/claude-3.5-sonnet";
    /// Meta Llama 3.1 70B Instruct
    pub const LLAMA_3_1_70B: &str = "meta-llama/llama-3.1-70b-instruct";
    /// Meta Llama 3.1 8B Instruct (fast)
    pub const LLAMA_3_1_8B: &str = "meta-llama/llama-3.1-8b-instruct";
    /// Mistral Large
    pub const MISTRAL_LARGE: &str = "mistralai/mistral-large";
    /// Mistral Small (fast)
    pub const MISTRAL_SMALL: &str = "mistralai/mistral-small";
}

impl ::zeroclaw_api::attribution::Attributable for TelnyxModelProvider {
    fn role(&self) -> ::zeroclaw_api::attribution::Role {
        ::zeroclaw_api::attribution::Role::Provider(
            ::zeroclaw_api::attribution::ProviderKind::Model(
                ::zeroclaw_api::attribution::ModelProviderKind::Telnyx,
            ),
        )
    }
    fn alias(&self) -> &str {
        &self.alias
    }
}

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

    #[test]
    fn creates_provider_with_key() {
        let model_provider = TelnyxModelProvider::builder("test")
            .api_key(Some("test-key"))
            .build();
        assert!(model_provider.api_key.is_some());
    }

    #[test]
    fn creates_provider_without_key() {
        let _provider = TelnyxModelProvider::builder("test").api_key(None).build();
        // Will be None if env vars not set
    }

    #[test]
    fn model_constants_are_valid() {
        assert!(models::GPT_4O.starts_with("openai/"));
        assert!(models::CLAUDE_3_5_SONNET.starts_with("anthropic/"));
        assert!(models::LLAMA_3_1_70B.starts_with("meta-llama/"));
        assert!(models::MISTRAL_LARGE.starts_with("mistralai/"));
    }

    #[test]
    fn resolve_key_from_parameter() {
        let key = resolve_telnyx_api_key(Some("direct-key"));
        assert_eq!(key, Some("direct-key".to_string()));
    }

    #[test]
    fn resolve_key_trims_whitespace() {
        let key = resolve_telnyx_api_key(Some("  spaced-key  "));
        assert_eq!(key, Some("spaced-key".to_string()));
    }

    #[test]
    fn models_response_deserializes() {
        let json = r#"{
            "data": [
                {"id": "openai/gpt-4o"},
                {"id": "anthropic/claude-3.5-sonnet"}
            ]
        }"#;

        let response: ModelsResponse = serde_json::from_str(json).unwrap();
        assert_eq!(response.data.len(), 2);
        assert_eq!(response.data[0].id, "openai/gpt-4o");
    }

    #[test]
    fn chat_request_serializes() {
        let req = ChatRequest {
            model: "openai/gpt-4o".to_string(),
            messages: vec![
                Message {
                    role: "system".to_string(),
                    content: "You are helpful.".to_string(),
                },
                Message {
                    role: "user".to_string(),
                    content: "Hello".to_string(),
                },
            ],
            temperature: Some(0.7),
        };

        let json = serde_json::to_string(&req).unwrap();
        assert!(json.contains("openai/gpt-4o"));
        assert!(json.contains("system"));
        assert!(json.contains("user"));
    }

    #[test]
    fn chat_response_deserializes() {
        let json = r#"{"choices":[{"message":{"content":"Hello from Telnyx!"}}]}"#;
        let resp: ChatResponse = serde_json::from_str(json).unwrap();
        assert_eq!(resp.choices[0].message.content, "Hello from Telnyx!");
    }
}
