//! SSE (Server-Sent Events) client for OpenCode API
//!
//! Provides cross-platform SSE streaming for real-time event updates

use crate::types::Event;
use futures::stream::Stream;
use std::pin::Pin;

/// SSE-specific errors
#[derive(Debug)]
pub enum SseError {
    Network(String),
    Parse(String),
    Closed,
}

impl std::fmt::Display for SseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SseError::Network(msg) => write!(f, "Network error: {}", msg),
            SseError::Parse(msg) => write!(f, "Parse error: {}", msg),
            SseError::Closed => write!(f, "Connection closed"),
        }
    }
}

impl std::error::Error for SseError {}

/// Connection state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionState {
    Connecting,
    Connected,
    Disconnected,
    Error,
}

/// Stream of SSE events
pub type SseStream = Pin<Box<dyn Stream<Item = Result<Event, SseError>> + Send>>;

/// SSE Client
pub struct SseClient {
    base_url: String,
}

impl SseClient {
    /// Create new SSE client
    pub fn new(base_url: impl Into<String>) -> Self {
        Self {
            base_url: base_url.into(),
        }
    }

    /// Connect to SSE endpoint and return event stream
    pub fn connect(&self) -> Result<SseStream, SseError> {
        let url = format!("{}/event", self.base_url);

        #[cfg(target_arch = "wasm32")]
        {
            wasm::connect_sse(&url)
        }

        #[cfg(not(target_arch = "wasm32"))]
        {
            native::connect_sse(&url)
        }
    }
}

#[cfg(target_arch = "wasm32")]
mod wasm {
    use super::*;
    use futures::channel::mpsc;
    use wasm_bindgen::prelude::*;
    use wasm_bindgen::JsCast;
    use web_sys::{EventSource, MessageEvent};

    pub fn connect_sse(url: &str) -> Result<SseStream, SseError> {
        web_sys::console::log_1(&format!("SSE: Creating EventSource for {}", url).into());

        let event_source = EventSource::new(url)
            .map_err(|e| SseError::Network(format!("EventSource creation failed: {:?}", e)))?;

        web_sys::console::log_1(
            &format!(
                "SSE: EventSource created, readyState: {}",
                event_source.ready_state()
            )
            .into(),
        );

        let (tx, rx) = mpsc::unbounded::<Result<Event, SseError>>();

        setup_listeners(&event_source, tx)?;

        Ok(Box::pin(rx))
    }

    fn setup_listeners(
        source: &EventSource,
        mut tx: mpsc::UnboundedSender<Result<Event, SseError>>,
    ) -> Result<(), SseError> {
        // Add open event listener
        {
            let cb = Closure::wrap(Box::new(move |_e: web_sys::Event| {
                web_sys::console::log_1(&"SSE: Connection opened".into());
            }) as Box<dyn FnMut(web_sys::Event)>);

            source
                .add_event_listener_with_callback("open", cb.as_ref().unchecked_ref())
                .map_err(|e| SseError::Network(format!("Open listener setup failed: {:?}", e)))?;

            cb.forget();
        }

        // Add error event listener
        {
            let cb = Closure::wrap(Box::new(move |e: web_sys::Event| {
                web_sys::console::error_1(&format!("SSE: Error event: {:?}", e).into());
            }) as Box<dyn FnMut(web_sys::Event)>);

            source
                .add_event_listener_with_callback("error", cb.as_ref().unchecked_ref())
                .map_err(|e| SseError::Network(format!("Error listener setup failed: {:?}", e)))?;

            cb.forget();
        }

        let events = [
            "message.updated",
            "message.removed",
            "message.part.updated",
            "message.part.removed",
            "session.updated",
            "session.created",
            "session.deleted",
            "session.compacted",
            "server.connected",
            "session.idle",
            "session.error",
            "permission.updated",
            "file.edited",
            "todo.updated",
        ];

        web_sys::console::log_1(
            &format!("SSE: Setting up listeners for {} event types", events.len()).into(),
        );

        for event_type in events {
            let tx_clone = tx.clone();
            let evt = event_type.to_string();
            let evt_for_log = evt.clone();

            let cb = Closure::wrap(Box::new(move |e: MessageEvent| {
                web_sys::console::log_1(&format!("SSE: Received {} event", evt_for_log).into());

                if let Some(data) = e.data().as_string() {
                    web_sys::console::log_1(&format!("SSE: Event data: {}", data).into());

                    // OpenCode sends the event type INSIDE the data JSON, not in the event: field
                    // So we parse the data directly instead of wrapping it
                    match serde_json::from_str(&data) {
                        Ok(event) => {
                            web_sys::console::log_1(
                                &format!("SSE: Parsed event successfully").into(),
                            );
                            let _ = tx_clone.unbounded_send(Ok(event));
                        }
                        Err(e) => {
                            web_sys::console::error_1(&format!("SSE: Parse error: {}", e).into());
                            let _ = tx_clone.unbounded_send(Err(SseError::Parse(e.to_string())));
                        }
                    }
                } else {
                    web_sys::console::warn_1(
                        &format!("SSE: {} event has no data", evt_for_log).into(),
                    );
                }
            }) as Box<dyn FnMut(MessageEvent)>);

            source
                .add_event_listener_with_callback(event_type, cb.as_ref().unchecked_ref())
                .map_err(|e| SseError::Network(format!("Listener setup failed: {:?}", e)))?;

            cb.forget();
        }

        web_sys::console::log_1(&"SSE: All listeners setup complete".into());
        Ok(())
    }
}

#[cfg(not(target_arch = "wasm32"))]
mod native {
    use super::*;
    use futures::channel::mpsc;
    use futures::stream::StreamExt;
    use futures::SinkExt;

    pub fn connect_sse(url: &str) -> Result<SseStream, SseError> {
        let url = url.to_string();
        let (tx, rx) = mpsc::unbounded::<Result<Event, SseError>>();

        tokio::spawn(async move {
            let _ = stream_events(url, tx).await;
        });

        Ok(Box::pin(rx))
    }

    async fn stream_events(
        url: String,
        mut tx: mpsc::UnboundedSender<Result<Event, SseError>>,
    ) -> Result<(), SseError> {
        let response = reqwest::get(&url)
            .await
            .map_err(|e| SseError::Network(e.to_string()))?;

        let mut stream = response.bytes_stream();
        let mut buffer = String::new();

        while let Some(chunk_result) = stream.next().await {
            let chunk = chunk_result.map_err(|e| SseError::Network(e.to_string()))?;
            buffer.push_str(&String::from_utf8_lossy(&chunk));

            while let Some((event, remaining)) = parse_sse(&buffer) {
                buffer = remaining;
                if tx.send(Ok(event)).await.is_err() {
                    return Ok(());
                }
            }
        }

        let _ = tx.send(Err(SseError::Closed)).await;
        Ok(())
    }

    /// Filter system prompts from JSON data for cleaner logging
    fn filter_system_prompt(data: &str) -> String {
        // Try to parse as JSON and filter out system prompts
        match serde_json::from_str::<serde_json::Value>(data) {
            Ok(mut json) => {
                if let Some(properties) = json.get_mut("properties") {
                    if let Some(info) = properties.get_mut("info") {
                        if let Some(system) = info.get_mut("system") {
                            if let Some(arr) = system.as_array() {
                                if !arr.is_empty() {
                                    *system = serde_json::json!(format!("[FILTERED {} items]", arr.len()));
                                }
                            }
                        }
                    }
                }
                serde_json::to_string(&json).unwrap_or_else(|_| data.to_string())
            }
            Err(_) => data.to_string()
        }
    }

    fn parse_sse(buffer: &str) -> Option<(Event, String)> {
        let mut event_type = String::new();
        let mut data_lines = Vec::new();
        let mut pos = 0;

        for line in buffer.lines() {
            pos += line.len() + 1;

            if line.is_empty() {
                if !data_lines.is_empty() {
                    let data = data_lines.join("\n");

                    // Filter system prompts for cleaner logging
                    let filtered_data = filter_system_prompt(&data);
                    eprintln!("[OpenCode SSE] event_type='{}', data={}", event_type, filtered_data);

                    // OpenCode sends the event type INSIDE the data JSON, not in the event: field
                    // So we parse the data directly instead of wrapping it
                    match serde_json::from_str(&data) {
                        Ok(event) => {
                            eprintln!("[OpenCode SSE] Parsed event successfully");
                            return Some((event, buffer[pos..].to_string()));
                        }
                        Err(e) => {
                            eprintln!("[OpenCode SSE] Parse error: {} for data: {}", e, filtered_data);
                            return None;
                        }
                    }
                }
            } else if let Some(val) = line.strip_prefix("event:") {
                event_type = val.trim().to_string();
            } else if let Some(val) = line.strip_prefix("data:") {
                data_lines.push(val.trim().to_string());
            }
        }

        None
    }
}
