/**
 * WebSocket Client for MK3 TUI
 * Connects to Node.js backend WebSocket server
 * Handles bidirectional JSON message communication
 */
use anyhow::{Context, Result};
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc;
use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage};

// Message types from STEP-2-COMPLETE-SPECIFICATION.md
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum Message {
    #[serde(rename = "command")]
    Command(CommandMessage),
    #[serde(rename = "response")]
    Response(ResponseMessage),
    #[serde(rename = "event")]
    Event(EventMessage),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandMessage {
    pub id: String,
    pub timestamp: String,
    pub payload: CommandPayload,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandPayload {
    pub command: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseMessage {
    pub id: String,
    pub timestamp: String,
    pub payload: ResponsePayload,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponsePayload {
    pub success: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventMessage {
    pub timestamp: String,
    pub payload: EventPayload,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventPayload {
    pub event: String,
    pub data: serde_json::Value,
}

pub enum WsClientMessage {
    Connected,
    Disconnected,
    Response(ResponseMessage),
    Event(EventMessage),
    Error(String),
    ConnectionLost,
    #[allow(dead_code)]
    Reconnecting,
    #[allow(dead_code)]
    ReconnectFailed(String),
}

pub struct WebSocketClient {
    #[allow(dead_code)]
    url: String,
    tx: mpsc::UnboundedSender<WsMessage>,
    rx: Arc<Mutex<mpsc::UnboundedReceiver<WsClientMessage>>>,
    connected: Arc<Mutex<bool>>,
    pending_commands: Arc<Mutex<Vec<(String, String, Option<serde_json::Value>)>>>, // (id, command, data)
}

impl WebSocketClient {
    pub async fn connect(url: &str) -> Result<Self> {
        let (msg_tx, msg_rx) = mpsc::unbounded_channel::<WsMessage>();
        let (client_tx, client_rx) = mpsc::unbounded_channel::<WsClientMessage>();

        let url_clone = url.to_string();
        let connected = Arc::new(Mutex::new(false));
        let connected_clone = connected.clone();

        // Spawn WebSocket connection task
        tokio::spawn(async move {
            if let Err(e) =
                Self::run_connection(&url_clone, client_tx.clone(), msg_rx, connected_clone).await
            {
                let _ = client_tx.send(WsClientMessage::Error(e.to_string()));
            }
        });

        Ok(Self {
            url: url.to_string(),
            tx: msg_tx,
            rx: Arc::new(Mutex::new(client_rx)),
            connected,
            pending_commands: Arc::new(Mutex::new(Vec::new())),
        })
    }

    async fn run_connection(
        url: &str,
        client_tx: mpsc::UnboundedSender<WsClientMessage>,
        mut client_rx: mpsc::UnboundedReceiver<WsMessage>,
        connected: Arc<Mutex<bool>>,
    ) -> Result<()> {
        // Connect to WebSocket server
        let (ws_stream, _) = connect_async(url)
            .await
            .context("Failed to connect to WebSocket server")?;

        *connected.lock().unwrap() = true;
        let _ = client_tx.send(WsClientMessage::Connected);

        let (mut write, mut read) = ws_stream.split();

        // Spawn task to handle outgoing messages
        tokio::spawn(async move {
            while let Some(msg) = client_rx.recv().await {
                if let Err(e) = write.send(msg).await {
                    eprintln!("[WebSocket] Error sending message: {}", e);
                    break;
                }
            }
        });

        // Handle incoming messages
        while let Some(msg) = read.next().await {
            match msg {
                Ok(WsMessage::Text(text)) => {
                    if let Err(e) = Self::handle_message(&text, &client_tx) {
                        eprintln!("[WebSocket] Error handling message: {}", e);
                    }
                }
                Ok(WsMessage::Close(_)) => {
                    *connected.lock().unwrap() = false;
                    let _ = client_tx.send(WsClientMessage::Disconnected);
                    break;
                }
                Err(e) => {
                    *connected.lock().unwrap() = false;
                    let _ = client_tx.send(WsClientMessage::ConnectionLost);
                    let _ =
                        client_tx.send(WsClientMessage::Error(format!("Connection error: {}", e)));
                    break;
                }
                _ => {}
            }
        }

        Ok(())
    }

    fn handle_message(
        text: &str,
        client_tx: &mpsc::UnboundedSender<WsClientMessage>,
    ) -> Result<()> {
        let message: Message = serde_json::from_str(text).context("Failed to parse message")?;

        match message {
            Message::Response(resp) => {
                let _ = client_tx.send(WsClientMessage::Response(resp));
            }
            Message::Event(event) => {
                let _ = client_tx.send(WsClientMessage::Event(event));
            }
            Message::Command(_) => {
                // Client should not receive commands
                eprintln!("[WebSocket] Unexpected command message from server");
            }
        }

        Ok(())
    }

    pub fn send_command(&self, command: &str, data: Option<serde_json::Value>) -> Result<String> {
        let id = format!("req-{}", uuid::Uuid::new_v4());
        let timestamp = chrono::Utc::now().to_rfc3339();

        let cmd = Message::Command(CommandMessage {
            id: id.clone(),
            timestamp,
            payload: CommandPayload {
                command: command.to_string(),
                data: data.clone(),
            },
        });

        let json = serde_json::to_string(&cmd)?;

        // Track pending command for retry
        if self.is_connected() {
            self.pending_commands
                .lock()
                .unwrap()
                .push((id.clone(), command.to_string(), data));
        }

        self.tx.send(WsMessage::Text(json))?;

        Ok(id)
    }

    /// Retry pending commands after reconnection
    #[allow(dead_code)]
    pub fn retry_pending_commands(&self) -> Result<Vec<String>> {
        let mut pending = self.pending_commands.lock().unwrap();
        let mut retried_ids = Vec::new();

        for (old_id, command, data) in pending.drain(..) {
            // Generate new ID for retry
            let new_id = format!("retry-{}", uuid::Uuid::new_v4());
            let timestamp = chrono::Utc::now().to_rfc3339();

            let cmd = Message::Command(CommandMessage {
                id: new_id.clone(),
                timestamp,
                payload: CommandPayload { command, data },
            });

            let json = serde_json::to_string(&cmd)?;
            self.tx.send(WsMessage::Text(json))?;

            retried_ids.push(format!(
                "{} -> {}",
                &old_id[old_id.len().saturating_sub(8)..],
                &new_id[new_id.len().saturating_sub(8)..]
            ));
        }

        Ok(retried_ids)
    }

    /// Clear pending commands (e.g., on explicit disconnect)
    #[allow(dead_code)]
    pub fn clear_pending_commands(&self) {
        self.pending_commands.lock().unwrap().clear();
    }

    pub fn try_recv(&self) -> Option<WsClientMessage> {
        self.rx.lock().unwrap().try_recv().ok()
    }

    pub fn is_connected(&self) -> bool {
        *self.connected.lock().unwrap()
    }
}

// Helper for creating UUID
mod uuid {
    use std::sync::atomic::{AtomicU64, Ordering};

    static COUNTER: AtomicU64 = AtomicU64::new(0);

    pub struct Uuid;

    impl Uuid {
        pub fn new_v4() -> String {
            let count = COUNTER.fetch_add(1, Ordering::SeqCst);
            let timestamp = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs();
            format!("{:016x}{:016x}", timestamp, count)
        }
    }
}

// Helper for timestamps
mod chrono {
    pub struct Utc;

    impl Utc {
        pub fn now() -> DateTime {
            DateTime
        }
    }

    pub struct DateTime;

    impl DateTime {
        pub fn to_rfc3339(&self) -> String {
            let now = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap();

            // Simple ISO 8601 format
            format!(
                "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
                1970,
                1,
                1, // Simplified - real impl would calculate actual date
                (now.as_secs() / 3600) % 24,
                (now.as_secs() / 60) % 60,
                now.as_secs() % 60
            )
        }
    }
}
