//! LLM Gateway — Stream Engine (Phase-80: T80.1 + T80.2)
//!
//! Converts `Stream<StreamEvent>` into SSE-compatible event strings.
//!
//! # SSE Event Format
//!
//! Each `StreamEvent` variant maps to a named SSE event:
//!
//! | StreamEvent variant | SSE `event:` name | `data:` payload |
//! |---|---|---|
//! | TextDelta | `text_delta` | `{"content":"..."}` |
//! | ThinkingDelta | `thinking_delta` | `{"content":"..."}` |
//! | AudioDelta | `audio_delta` | `{"data":"...","format":"...","sample_rate":N}` |
//! | ImageGenerated | `image_generated` | `{"url":"...","mime_type":"..."}` |
//! | ToolCallDelta | `tool_call` | `{"id":"...","name":"...","arguments_delta":"..."}` |
//! | ToolResult | `tool_result` | `{"call_id":"...","result":"..."}` |
//! | Usage | `usage` | `{...TokenUsage...}` |
//! | RagContext | `rag_context` | `{"sources":[...],"relevance_scores":[...]}` |
//! | Done | `done` | `{"message_id":"...","finish_reason":"..."}` |
//! | Error | `error` | `{"code":"...","message":"..."}` |
//!
//! # Heartbeat
//!
//! A `:heartbeat` comment is sent every `heartbeat_interval` to keep connections alive.

use serde::{Deserialize, Serialize};
use serde_json::json;

use super::error::LlmResult;
use super::types::{AudioFormat, FinishReason, StreamEvent, TokenUsage};

// ============================================================================
// SSE Event representation
// ============================================================================

/// A single SSE event ready for transmission.
#[derive(Debug, Clone)]
pub struct SseEvent {
    /// SSE event name (e.g., "text_delta", "done").
    pub event: String,
    /// JSON data payload.
    pub data: String,
}

impl SseEvent {
    /// Create a heartbeat comment event.
    pub fn heartbeat() -> Self {
        Self {
            event: "heartbeat".to_string(),
            data: String::new(),
        }
    }

    /// Format as SSE text (ready to write to HTTP response).
    ///
    /// ```text
    /// event: text_delta
    /// data: {"content":"Hello"}
    ///
    /// ```
    pub fn to_sse_string(&self) -> String {
        if self.event == "heartbeat" {
            ":heartbeat\n\n".to_string()
        } else {
            format!("event: {}\ndata: {}\n\n", self.event, self.data)
        }
    }
}

// ============================================================================
// StreamEvent → SseEvent conversion
// ============================================================================

/// Convert a `StreamEvent` into an `SseEvent` for SSE transmission.
pub fn stream_event_to_sse(event: &StreamEvent) -> SseEvent {
    match event {
        StreamEvent::TextDelta { content } => SseEvent {
            event: "text_delta".to_string(),
            data: json!({ "content": content }).to_string(),
        },

        StreamEvent::ThinkingDelta { content } => SseEvent {
            event: "thinking_delta".to_string(),
            data: json!({ "content": content }).to_string(),
        },

        StreamEvent::AudioDelta {
            data,
            format,
            sample_rate,
        } => SseEvent {
            event: "audio_delta".to_string(),
            data: json!({
                "data": data,
                "format": format_audio(format),
                "sample_rate": sample_rate,
            })
            .to_string(),
        },

        StreamEvent::ImageGenerated { url, mime_type } => SseEvent {
            event: "image_generated".to_string(),
            data: json!({
                "url": url,
                "mime_type": mime_type,
            })
            .to_string(),
        },

        StreamEvent::ToolCallDelta {
            id,
            name,
            arguments_delta,
        } => SseEvent {
            event: "tool_call".to_string(),
            data: json!({
                "id": id,
                "name": name,
                "arguments_delta": arguments_delta,
            })
            .to_string(),
        },

        StreamEvent::ToolResult { call_id, result } => SseEvent {
            event: "tool_result".to_string(),
            data: json!({
                "call_id": call_id,
                "result": result,
            })
            .to_string(),
        },

        StreamEvent::Usage(usage) => SseEvent {
            event: "usage".to_string(),
            data: serde_json::to_string(usage).unwrap_or_default(),
        },

        StreamEvent::RagContext {
            sources,
            relevance_scores,
        } => SseEvent {
            event: "rag_context".to_string(),
            data: json!({
                "sources": sources,
                "relevance_scores": relevance_scores,
            })
            .to_string(),
        },

        StreamEvent::AgentPlan { plan, step_count } => SseEvent {
            event: "agent_plan".to_string(),
            data: json!({
                "plan": plan,
                "step_count": step_count,
            })
            .to_string(),
        },

        StreamEvent::AgentThought { step, content } => SseEvent {
            event: "agent_thought".to_string(),
            data: json!({
                "step": step,
                "content": content,
            })
            .to_string(),
        },

        StreamEvent::AgentAction {
            step,
            tool_name,
            tool_args,
        } => SseEvent {
            event: "agent_action".to_string(),
            data: json!({
                "step": step,
                "tool_name": tool_name,
                "tool_args": tool_args,
            })
            .to_string(),
        },

        StreamEvent::AgentObservation {
            step,
            tool_name,
            success,
            output,
        } => SseEvent {
            event: "agent_observation".to_string(),
            data: json!({
                "step": step,
                "tool_name": tool_name,
                "success": success,
                "output": output,
            })
            .to_string(),
        },

        StreamEvent::Done {
            message_id,
            finish_reason,
        } => SseEvent {
            event: "done".to_string(),
            data: json!({
                "message_id": message_id,
                "finish_reason": format_finish_reason(finish_reason),
            })
            .to_string(),
        },

        StreamEvent::Error { code, message } => SseEvent {
            event: "error".to_string(),
            data: json!({
                "code": code,
                "message": message,
            })
            .to_string(),
        },
    }
}

// ============================================================================
// SseEvent → StreamEvent deserialization (for client SDK)
// ============================================================================

/// Parse an SSE event string back into a `StreamEvent`.
///
/// Input format: `event: <name>\ndata: <json>\n\n`
pub fn parse_sse_event(event_name: &str, data: &str) -> LlmResult<StreamEvent> {
    use super::error::LlmError;

    match event_name {
        "text_delta" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::TextDelta {
                content: v["content"].as_str().unwrap_or("").to_string(),
            })
        }

        "thinking_delta" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::ThinkingDelta {
                content: v["content"].as_str().unwrap_or("").to_string(),
            })
        }

        "audio_delta" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::AudioDelta {
                data: v["data"].as_str().unwrap_or("").to_string(),
                format: parse_audio_format(v["format"].as_str().unwrap_or("pcm16")),
                sample_rate: v["sample_rate"].as_u64().unwrap_or(16000) as u32,
            })
        }

        "image_generated" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::ImageGenerated {
                url: v["url"].as_str().unwrap_or("").to_string(),
                mime_type: v["mime_type"].as_str().unwrap_or("image/png").to_string(),
            })
        }

        "tool_call" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::ToolCallDelta {
                id: v["id"].as_str().unwrap_or("").to_string(),
                name: v["name"].as_str().unwrap_or("").to_string(),
                arguments_delta: v["arguments_delta"].as_str().unwrap_or("").to_string(),
            })
        }

        "tool_result" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::ToolResult {
                call_id: v["call_id"].as_str().unwrap_or("").to_string(),
                result: v["result"].as_str().unwrap_or("").to_string(),
            })
        }

        "usage" => {
            let usage: TokenUsage = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::Usage(usage))
        }

        "rag_context" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::RagContext {
                sources: v["sources"]
                    .as_array()
                    .cloned()
                    .unwrap_or_default(),
                relevance_scores: v["relevance_scores"]
                    .as_array()
                    .map(|a| a.iter().filter_map(|v| v.as_f64().map(|f| f as f32)).collect())
                    .unwrap_or_default(),
            })
        }

        "done" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::Done {
                message_id: v["message_id"].as_str().unwrap_or("").to_string(),
                finish_reason: parse_finish_reason(
                    v["finish_reason"].as_str().unwrap_or("stop"),
                ),
            })
        }

        "error" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::Error {
                code: v["code"].as_str().unwrap_or("unknown").to_string(),
                message: v["message"].as_str().unwrap_or("").to_string(),
            })
        }

        // Agent events (Phase-87)
        "agent_plan" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::AgentPlan {
                plan: v["plan"].as_str().unwrap_or("").to_string(),
                step_count: v["step_count"].as_u64().unwrap_or(0) as usize,
            })
        }

        "agent_thought" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::AgentThought {
                step: v["step"].as_u64().unwrap_or(0) as usize,
                content: v["content"].as_str().unwrap_or("").to_string(),
            })
        }

        "agent_action" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::AgentAction {
                step: v["step"].as_u64().unwrap_or(0) as usize,
                tool_name: v["tool_name"].as_str().unwrap_or("").to_string(),
                tool_args: v["tool_args"].as_str().unwrap_or("").to_string(),
            })
        }

        "agent_observation" => {
            let v: serde_json::Value = serde_json::from_str(data)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            Ok(StreamEvent::AgentObservation {
                step: v["step"].as_u64().unwrap_or(0) as usize,
                tool_name: v["tool_name"].as_str().unwrap_or("").to_string(),
                success: v["success"].as_bool().unwrap_or(false),
                output: v["output"].as_str().unwrap_or("").to_string(),
            })
        }

        other => Err(LlmError::ResponseParseError(format!(
            "Unknown SSE event type: '{other}'"
        ))),
    }
}

// ============================================================================
// Helpers
// ============================================================================

fn format_audio(fmt: &AudioFormat) -> &'static str {
    match fmt {
        AudioFormat::Pcm16 => "pcm16",
        AudioFormat::Mp3 => "mp3",
        AudioFormat::Opus => "opus",
        AudioFormat::Wav => "wav",
        AudioFormat::Flac => "flac",
    }
}

fn parse_audio_format(s: &str) -> AudioFormat {
    match s {
        "mp3" => AudioFormat::Mp3,
        "opus" => AudioFormat::Opus,
        "wav" => AudioFormat::Wav,
        "flac" => AudioFormat::Flac,
        _ => AudioFormat::Pcm16,
    }
}

fn format_finish_reason(reason: &FinishReason) -> &'static str {
    match reason {
        FinishReason::Stop => "stop",
        FinishReason::Length => "length",
        FinishReason::ToolCalls => "tool_calls",
        FinishReason::ContentFilter => "content_filter",
        FinishReason::Cancelled => "cancelled",
        FinishReason::Other(_) => "other",
    }
}

fn parse_finish_reason(s: &str) -> FinishReason {
    match s {
        "stop" => FinishReason::Stop,
        "length" => FinishReason::Length,
        "tool_calls" => FinishReason::ToolCalls,
        "content_filter" => FinishReason::ContentFilter,
        "cancelled" => FinishReason::Cancelled,
        other => FinishReason::Other(other.to_string()),
    }
}

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

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

    #[test]
    fn test_text_delta_roundtrip() {
        let event = StreamEvent::TextDelta {
            content: "Hello, world!".to_string(),
        };
        let sse = stream_event_to_sse(&event);
        assert_eq!(sse.event, "text_delta");
        assert!(sse.data.contains("Hello, world!"));

        // Parse back
        let parsed = parse_sse_event(&sse.event, &sse.data).unwrap();
        match parsed {
            StreamEvent::TextDelta { content } => assert_eq!(content, "Hello, world!"),
            _ => panic!("Expected TextDelta"),
        }
    }

    #[test]
    fn test_thinking_delta_roundtrip() {
        let event = StreamEvent::ThinkingDelta {
            content: "Let me think...".to_string(),
        };
        let sse = stream_event_to_sse(&event);
        assert_eq!(sse.event, "thinking_delta");

        let parsed = parse_sse_event(&sse.event, &sse.data).unwrap();
        match parsed {
            StreamEvent::ThinkingDelta { content } => assert_eq!(content, "Let me think..."),
            _ => panic!("Expected ThinkingDelta"),
        }
    }

    #[test]
    fn test_usage_roundtrip() {
        let usage = TokenUsage::new(100, 50);
        let event = StreamEvent::Usage(usage);
        let sse = stream_event_to_sse(&event);
        assert_eq!(sse.event, "usage");

        let parsed = parse_sse_event(&sse.event, &sse.data).unwrap();
        match parsed {
            StreamEvent::Usage(u) => {
                assert_eq!(u.prompt_tokens, 100);
                assert_eq!(u.completion_tokens, 50);
                assert_eq!(u.total_tokens, 150);
            }
            _ => panic!("Expected Usage"),
        }
    }

    #[test]
    fn test_done_roundtrip() {
        let event = StreamEvent::Done {
            message_id: "msg-123".to_string(),
            finish_reason: FinishReason::Stop,
        };
        let sse = stream_event_to_sse(&event);
        assert_eq!(sse.event, "done");

        let parsed = parse_sse_event(&sse.event, &sse.data).unwrap();
        match parsed {
            StreamEvent::Done {
                message_id,
                finish_reason,
            } => {
                assert_eq!(message_id, "msg-123");
                assert!(matches!(finish_reason, FinishReason::Stop));
            }
            _ => panic!("Expected Done"),
        }
    }

    #[test]
    fn test_error_roundtrip() {
        let event = StreamEvent::Error {
            code: "rate_limit".to_string(),
            message: "Too many requests".to_string(),
        };
        let sse = stream_event_to_sse(&event);
        assert_eq!(sse.event, "error");

        let parsed = parse_sse_event(&sse.event, &sse.data).unwrap();
        match parsed {
            StreamEvent::Error { code, message } => {
                assert_eq!(code, "rate_limit");
                assert_eq!(message, "Too many requests");
            }
            _ => panic!("Expected Error"),
        }
    }

    #[test]
    fn test_tool_call_roundtrip() {
        let event = StreamEvent::ToolCallDelta {
            id: "call_1".to_string(),
            name: "search".to_string(),
            arguments_delta: r#"{"query":"rust"}"#.to_string(),
        };
        let sse = stream_event_to_sse(&event);
        assert_eq!(sse.event, "tool_call");

        let parsed = parse_sse_event(&sse.event, &sse.data).unwrap();
        match parsed {
            StreamEvent::ToolCallDelta {
                id,
                name,
                arguments_delta,
            } => {
                assert_eq!(id, "call_1");
                assert_eq!(name, "search");
                assert!(arguments_delta.contains("rust"));
            }
            _ => panic!("Expected ToolCallDelta"),
        }
    }

    #[test]
    fn test_sse_string_format() {
        let event = StreamEvent::TextDelta {
            content: "Hi".to_string(),
        };
        let sse = stream_event_to_sse(&event);
        let s = sse.to_sse_string();
        assert!(s.starts_with("event: text_delta\n"));
        assert!(s.contains("data: "));
        assert!(s.ends_with("\n\n"));
    }

    #[test]
    fn test_heartbeat() {
        let hb = SseEvent::heartbeat();
        assert_eq!(hb.to_sse_string(), ":heartbeat\n\n");
    }

    #[test]
    fn test_rag_context_roundtrip() {
        let event = StreamEvent::RagContext {
            sources: vec![json!({"title": "Doc 1"}), json!({"title": "Doc 2"})],
            relevance_scores: vec![0.95, 0.87],
        };
        let sse = stream_event_to_sse(&event);
        assert_eq!(sse.event, "rag_context");

        let parsed = parse_sse_event(&sse.event, &sse.data).unwrap();
        match parsed {
            StreamEvent::RagContext {
                sources,
                relevance_scores,
            } => {
                assert_eq!(sources.len(), 2);
                assert_eq!(relevance_scores.len(), 2);
                assert!((relevance_scores[0] - 0.95).abs() < 0.01);
            }
            _ => panic!("Expected RagContext"),
        }
    }

    #[test]
    fn test_unknown_event_error() {
        let result = parse_sse_event("unknown_event", "{}");
        assert!(result.is_err());
    }
}
