//! Google Gemini Native Client (Phase-78: T78.6)
//!
//! Implements `LlmClient` for the Google Gemini `generateContent` /
//! `streamGenerateContent` API protocol.
//!
//! # Key differences from OpenAI
//!
//! - Uses `system_instruction` field (not a system message in contents)
//! - Has `inlineData` for image generation output
//! - Different streaming event structure (array-wrapped)
//! - API key as query parameter `?key=...`
//! - Safety settings support

use std::pin::Pin;
use std::time::Instant;

use async_trait::async_trait;
use reqwest::Client as HttpClient;
use serde::Deserialize;
use serde_json::{json, Value};
use tokio::sync::mpsc;
use tracing::{debug, warn, trace};

use super::client::{ContentPart, LlmClient, LlmMessage, MessageRole};
use super::error::{LlmError, LlmResult};
use super::types::{
    ChatConfig, ChatResponse, FinishReason, LlmProvider, StreamEvent, TokenUsage, ToolCall,
};

// ============================================================================
// Gemini API types
// ============================================================================

#[derive(Debug, Deserialize)]
struct GeminiResponse {
    candidates: Option<Vec<GeminiCandidate>>,
    #[serde(rename = "usageMetadata")]
    usage_metadata: Option<GeminiUsage>,
    error: Option<GeminiErrorDetail>,
}

#[derive(Debug, Deserialize)]
struct GeminiCandidate {
    content: Option<GeminiContent>,
    #[serde(rename = "finishReason")]
    finish_reason: Option<String>,
}

#[derive(Debug, Deserialize)]
struct GeminiContent {
    parts: Option<Vec<GeminiPart>>,
    #[allow(dead_code)]
    role: Option<String>,
}

#[derive(Debug, Deserialize)]
struct GeminiPart {
    text: Option<String>,
    #[serde(rename = "inlineData")]
    inline_data: Option<GeminiInlineData>,
    #[serde(rename = "functionCall")]
    function_call: Option<GeminiFunctionCall>,
}

#[derive(Debug, Deserialize)]
struct GeminiInlineData {
    #[serde(rename = "mimeType")]
    mime_type: String,
    data: String,
}

#[derive(Debug, Deserialize)]
struct GeminiFunctionCall {
    name: String,
    args: Option<Value>,
}

#[derive(Debug, Deserialize)]
struct GeminiUsage {
    #[serde(rename = "promptTokenCount")]
    prompt_token_count: Option<u32>,
    #[serde(rename = "candidatesTokenCount")]
    candidates_token_count: Option<u32>,
    #[serde(rename = "totalTokenCount")]
    total_token_count: Option<u32>,
}

#[derive(Debug, Deserialize)]
struct GeminiErrorDetail {
    message: String,
    #[allow(dead_code)]
    code: Option<i32>,
}

// ============================================================================
// GeminiClient
// ============================================================================

/// Google Gemini native protocol client.
pub struct GeminiClient {
    provider: LlmProvider,
    http: HttpClient,
}

impl GeminiClient {
    /// Create a new Gemini client.
    pub fn new(provider: LlmProvider) -> LlmResult<Self> {
        let http = HttpClient::builder()
            .timeout(std::time::Duration::from_secs(provider.timeout_secs as u64))
            .build()
            .map_err(|e| LlmError::ConfigError(format!("Failed to create HTTP client: {e}")))?;

        Ok(Self { provider, http })
    }

    /// Build the Gemini API URL.
    fn api_url(&self, model: &str, stream: bool) -> String {
        let base = self.provider.base_url.trim_end_matches('/');
        let method = if stream {
            "streamGenerateContent"
        } else {
            "generateContent"
        };

        let api_key = self.provider.api_key.as_deref().unwrap_or("");

        // Standard Gemini API: https://generativelanguage.googleapis.com/v1beta
        if stream {
            format!("{base}/models/{model}:{method}?key={api_key}&alt=sse")
        } else {
            format!("{base}/models/{model}:{method}?key={api_key}")
        }
    }

    /// Convert LlmMessages to Gemini format.
    fn convert_messages(
        &self,
        messages: &[LlmMessage],
    ) -> (Option<Value>, Vec<Value>) {
        let mut system_instruction = None;
        let mut contents = Vec::new();

        for msg in messages {
            match msg.role {
                MessageRole::System => {
                    // Gemini uses system_instruction, not a content message
                    system_instruction = Some(json!({
                        "parts": [{"text": msg.content.clone()}]
                    }));
                }
                MessageRole::User => {
                    let parts = if msg.is_multimodal() {
                        msg.content_parts
                            .iter()
                            .map(|p| match p {
                                ContentPart::Text { text } => json!({"text": text}),
                                ContentPart::Image { url, mime_type } => {
                                    // Check if it's a base64 data URI
                                    if url.starts_with("data:") {
                                        // Extract base64 data
                                        let parts: Vec<&str> = url.splitn(2, ',').collect();
                                        let data = parts.get(1).unwrap_or(&"");
                                        let mime = mime_type
                                            .as_deref()
                                            .unwrap_or("image/png");
                                        json!({
                                            "inlineData": {
                                                "mimeType": mime,
                                                "data": data,
                                            }
                                        })
                                    } else {
                                        json!({"text": format!("[Image: {url}]")})
                                    }
                                }
                                ContentPart::Audio { url, format } => {
                                    if url.starts_with("data:") {
                                        let parts: Vec<&str> = url.splitn(2, ',').collect();
                                        let data = parts.get(1).unwrap_or(&"");
                                        let mime = format
                                            .as_deref()
                                            .map(|f| format!("audio/{f}"))
                                            .unwrap_or_else(|| "audio/wav".to_string());
                                        json!({
                                            "inlineData": {
                                                "mimeType": mime,
                                                "data": data,
                                            }
                                        })
                                    } else {
                                        json!({"text": format!("[Audio: {url}]")})
                                    }
                                }
                            })
                            .collect()
                    } else {
                        vec![json!({"text": msg.content.clone()})]
                    };
                    contents.push(json!({
                        "role": "user",
                        "parts": parts,
                    }));
                }
                MessageRole::Assistant => {
                    let mut parts = vec![];
                    if !msg.content.is_empty() {
                        parts.push(json!({"text": msg.content.clone()}));
                    }
                    for tc in &msg.tool_calls {
                        parts.push(json!({
                            "functionCall": {
                                "name": tc.name,
                                "args": serde_json::from_str::<Value>(&tc.arguments).unwrap_or(Value::Null),
                            }
                        }));
                    }
                    contents.push(json!({
                        "role": "model",
                        "parts": parts,
                    }));
                }
                MessageRole::Tool => {
                    contents.push(json!({
                        "role": "function",
                        "parts": [{
                            "functionResponse": {
                                "name": msg.name.as_deref().unwrap_or("function"),
                                "response": serde_json::from_str::<Value>(&msg.content).unwrap_or(json!({"result": msg.content})),
                            }
                        }],
                    }));
                }
            }
        }

        (system_instruction, contents)
    }

    /// Build request body.
    fn build_body(
        &self,
        messages: &[LlmMessage],
        config: &ChatConfig,
    ) -> Value {
        let (system_instruction, contents) = self.convert_messages(messages);

        let mut body = json!({
            "contents": contents,
        });

        if let Some(si) = system_instruction {
            body["systemInstruction"] = si;
        }

        // Generation config
        let mut gen_config = json!({});
        if let Some(temp) = config.temperature {
            gen_config["temperature"] = json!(temp);
        }
        if let Some(max_tokens) = config.max_tokens {
            gen_config["maxOutputTokens"] = json!(max_tokens);
        }
        if let Some(top_p) = config.top_p {
            gen_config["topP"] = json!(top_p);
        }
        if let Some(top_k) = config.top_k {
            gen_config["topK"] = json!(top_k);
        }
        if let Some(seed) = config.seed {
            gen_config["seed"] = json!(seed);
        }
        if !config.stop.is_empty() {
            gen_config["stopSequences"] = json!(config.stop);
        }
        if let Some(fmt) = &config.response_format {
            if fmt == "json_object" {
                gen_config["responseMimeType"] = json!("application/json");
            }
        }
        body["generationConfig"] = gen_config;

        // Tools
        if !config.tools.is_empty() {
            let functions: Vec<Value> = config
                .tools
                .iter()
                .map(|t| {
                    json!({
                        "name": t.function.name,
                        "description": t.function.description,
                        "parameters": t.function.parameters,
                    })
                })
                .collect();
            body["tools"] = json!([{"functionDeclarations": functions}]);
        }

        body
    }

    /// Parse Gemini finish reason.
    fn parse_finish_reason(reason: Option<&str>) -> FinishReason {
        match reason {
            Some("STOP") | None => FinishReason::Stop,
            Some("MAX_TOKENS") => FinishReason::Length,
            Some("SAFETY") => FinishReason::ContentFilter,
            Some(other) => FinishReason::Other(other.to_string()),
        }
    }
}

#[async_trait]
impl LlmClient for GeminiClient {
    async fn chat(
        &self,
        messages: &[LlmMessage],
        config: &ChatConfig,
    ) -> LlmResult<ChatResponse> {
        let model_name = &self.provider.id;
        let url = self.api_url(model_name, false);
        let body = self.build_body(messages, config);

        let start = Instant::now();
        let resp = self
            .http
            .post(&url)
            .json(&body)
            .send()
            .await
            .map_err(|e| LlmError::RequestError(format!("Gemini request error: {e}")))?;

        let duration_ms = start.elapsed().as_millis() as u64;
        let status = resp.status();

        if !status.is_success() {
            let text = resp.text().await.unwrap_or_default();
            if let Ok(err) = serde_json::from_str::<GeminiResponse>(&text) {
                if let Some(detail) = err.error {
                    return Err(LlmError::RequestError(format!(
                        "Gemini API error: {}", detail.message
                    )));
                }
            }
            return Err(LlmError::RequestError(format!("HTTP {status}: {text}")));
        }

        let gemini_resp: GeminiResponse = resp.json().await.map_err(|e| {
            LlmError::ResponseParseError(format!("Failed to parse Gemini response: {e}"))
        })?;

        let candidate = gemini_resp
            .candidates
            .and_then(|mut c| if c.is_empty() { None } else { Some(c.remove(0)) })
            .ok_or_else(|| LlmError::ResponseParseError("No candidates".into()))?;

        let mut content = String::new();
        let mut tool_calls = Vec::new();

        if let Some(parts) = candidate.content.and_then(|c| c.parts) {
            for part in parts {
                if let Some(text) = part.text {
                    content.push_str(&text);
                }
                if let Some(fc) = part.function_call {
                    tool_calls.push(ToolCall {
                        id: format!("call_{}", uuid::Uuid::new_v4()),
                        name: fc.name,
                        arguments: fc.args.map(|a| a.to_string()).unwrap_or_default(),
                    });
                }
            }
        }

        let usage = gemini_resp.usage_metadata.map(|u| TokenUsage {
            prompt_tokens: u.prompt_token_count.unwrap_or(0),
            completion_tokens: u.candidates_token_count.unwrap_or(0),
            total_tokens: u.total_token_count.unwrap_or(0),
            ..Default::default()
        });

        Ok(ChatResponse {
            content,
            thinking: None,
            tool_calls,
            usage,
            finish_reason: Self::parse_finish_reason(candidate.finish_reason.as_deref()),
            model: model_name.to_string(),
            duration_ms: Some(duration_ms),
        })
    }

    async fn chat_stream(
        &self,
        messages: &[LlmMessage],
        config: &ChatConfig,
    ) -> LlmResult<Pin<Box<dyn futures_core::Stream<Item = LlmResult<StreamEvent>> + Send>>> {
        let model_name = self.provider.id.clone();
        let url = self.api_url(&model_name, true);
        let body = self.build_body(messages, config);

        let resp = self
            .http
            .post(&url)
            .json(&body)
            .send()
            .await
            .map_err(|e| LlmError::RequestError(format!("Gemini stream request error: {e}")))?;

        if !resp.status().is_success() {
            let text = resp.text().await.unwrap_or_default();
            return Err(LlmError::RequestError(format!(
                "Gemini stream error: {text}"
            )));
        }

        let (tx, rx) = mpsc::channel::<LlmResult<StreamEvent>>(64);

        tokio::spawn(async move {
            use tokio_stream::StreamExt;

            let mut byte_stream = resp.bytes_stream();
            let mut buffer = String::new();

            while let Some(chunk_result) = byte_stream.next().await {
                let chunk = match chunk_result {
                    Ok(bytes) => match String::from_utf8(bytes.to_vec()) {
                        Ok(s) => s,
                        Err(e) => {
                            let _ = tx.send(Err(LlmError::StreamError(format!("Invalid UTF-8: {e}")))).await;
                            break;
                        }
                    },
                    Err(e) => {
                        let _ = tx.send(Err(LlmError::StreamError(format!("Stream error: {e}")))).await;
                        break;
                    }
                };

                buffer.push_str(&chunk);

                // Gemini SSE format: data: {...}\r\n\r\n
                while let Some(pos) = buffer.find("\n\n") {
                    let event_text = buffer[..pos].to_string();
                    buffer = buffer[pos + 2..].to_string();

                    for line in event_text.lines() {
                        let line = line.trim();
                        if let Some(data) = line.strip_prefix("data: ") {
                            match serde_json::from_str::<GeminiResponse>(data) {
                                Ok(resp) => {
                                    if let Some(candidates) = &resp.candidates {
                                        for candidate in candidates {
                                            if let Some(content) = &candidate.content {
                                                if let Some(parts) = &content.parts {
                                                    for part in parts {
                                                        if let Some(text) = &part.text {
                                                            if !text.is_empty() {
                                                                let _ = tx.send(Ok(StreamEvent::TextDelta { content: text.clone() })).await;
                                                            }
                                                        }
                                                        if let Some(fc) = &part.function_call {
                                                            let _ = tx.send(Ok(StreamEvent::ToolCallDelta {
                                                                id: format!("call_{}", uuid::Uuid::new_v4()),
                                                                name: fc.name.clone(),
                                                                arguments_delta: fc.args.as_ref().map(|a| a.to_string()).unwrap_or_default(),
                                                            })).await;
                                                        }
                                                        if let Some(inline) = &part.inline_data {
                                                            let _ = tx.send(Ok(StreamEvent::ImageGenerated {
                                                                url: format!("data:{};base64,{}", inline.mime_type, inline.data),
                                                                mime_type: inline.mime_type.clone(),
                                                            })).await;
                                                        }
                                                    }
                                                }
                                            }
                                            if let Some(reason) = &candidate.finish_reason {
                                                let _ = tx.send(Ok(StreamEvent::Done {
                                                    message_id: String::new(),
                                                    finish_reason: Self::parse_finish_reason(Some(reason.as_str())),
                                                })).await;
                                            }
                                        }
                                    }
                                    if let Some(usage) = &resp.usage_metadata {
                                        let _ = tx.send(Ok(StreamEvent::Usage(TokenUsage {
                                            prompt_tokens: usage.prompt_token_count.unwrap_or(0),
                                            completion_tokens: usage.candidates_token_count.unwrap_or(0),
                                            total_tokens: usage.total_token_count.unwrap_or(0),
                                            ..Default::default()
                                        }))).await;
                                    }
                                }
                                Err(e) => {
                                    trace!(err = %e, "Failed to parse Gemini SSE chunk");
                                }
                            }
                        }
                    }
                }
            }
        });

        let stream = tokio_stream::wrappers::ReceiverStream::new(rx);
        Ok(Box::pin(stream))
    }

    fn provider(&self) -> &LlmProvider {
        &self.provider
    }

    async fn health_check(&self) -> LlmResult<bool> {
        let base = self.provider.base_url.trim_end_matches('/');
        let api_key = self.provider.api_key.as_deref().unwrap_or("");
        let url = format!("{base}/models?key={api_key}");

        match self.http.get(&url).timeout(std::time::Duration::from_secs(10)).send().await {
            Ok(resp) => {
                let healthy = resp.status().is_success();
                debug!(provider = %self.provider.id, healthy, "Gemini health check");
                Ok(healthy)
            }
            Err(e) => {
                warn!(provider = %self.provider.id, err = %e, "Gemini health check failed");
                Ok(false)
            }
        }
    }
}

impl std::fmt::Debug for GeminiClient {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("GeminiClient")
            .field("provider_id", &self.provider.id)
            .finish()
    }
}
