use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;

use serde_json::{json, Value};

use crate::browser::{ws_protocol, BrowserBackend};
use crate::pipe::{Action, CommandOutput, ExecutionSurfaceMetadata, PipeError, Timing};
use crate::security::MacPolicy;

pub trait WsClient: Send + Sync {
    fn send_text(&self, payload: &str) -> Result<(), PipeError>;
    fn recv_text_timeout(&self, timeout: Duration) -> Result<String, PipeError>;
}

pub struct WsBrowserBackend<C: WsClient> {
    client: Arc<C>,
    mac_policy: MacPolicy,
    request_url: Mutex<String>,
    next_seq: AtomicU64,
    response_timeout: Duration,
    in_flight: Mutex<()>,
}

impl<C: WsClient> WsBrowserBackend<C> {
    pub fn new(client: Arc<C>, mac_policy: MacPolicy, request_url: impl Into<String>) -> Self {
        Self {
            client,
            mac_policy,
            request_url: Mutex::new(request_url.into()),
            next_seq: AtomicU64::new(1),
            response_timeout: Duration::from_secs(30),
            in_flight: Mutex::new(()),
        }
    }

    pub fn with_response_timeout(mut self, response_timeout: Duration) -> Self {
        self.response_timeout = response_timeout;
        self
    }
}

impl<C: WsClient> BrowserBackend for WsBrowserBackend<C> {
    fn invoke(
        &self,
        action: Action,
        params: Value,
        expected_domain: &str,
    ) -> Result<CommandOutput, PipeError> {
        let _guard = self
            .in_flight
            .lock()
            .map_err(|_| PipeError::Protocol("browser ws request lock poisoned".to_string()))?;

        self.mac_policy.validate(&action, expected_domain)?;

        let seq = self.next_seq.fetch_add(1, Ordering::Relaxed);
        let request_id = seq.to_string();
        let request_url = self
            .request_url
            .lock()
            .map_err(|_| PipeError::Protocol("browser ws request url lock poisoned".to_string()))?
            .clone();
        let encoded = ws_protocol::encode_v1_action(
            &action,
            &params,
            &request_url,
            Some(request_id.as_str()),
        )?;

        self.client.send_text(&encoded.payload)?;

        let status = Some(recv_status_frame(&*self.client, self.response_timeout)?);
        if let Some(status) = status {
            let status_code = parse_status_code(&status)?;
            if status_code != 0 {
                return Err(PipeError::Protocol(format!(
                    "browser returned non-zero status: {status_code}"
                )));
            }
        }

        if action == Action::Navigate {
            if let Some(url) = params.get("url").and_then(Value::as_str) {
                let mut request_url = self.request_url.lock().map_err(|_| {
                    PipeError::Protocol("browser ws request url lock poisoned".to_string())
                })?;
                *request_url = url.to_string();
            }
        }

        if let Some(callback) = encoded.callback {
            loop {
                let frame = self.client.recv_text_timeout(self.response_timeout)?;
                let decoded = ws_protocol::decode_callback_frame(&frame)?;
                if decoded.callback_name == callback.callback_name {
                    return Ok(CommandOutput {
                        seq,
                        success: true,
                        data: json!({ "text": decoded.response_text }),
                        aom_snapshot: vec![],
                        timing: Timing {
                            queue_ms: 0,
                            exec_ms: 0,
                        },
                    });
                }
            }
        }

        Ok(CommandOutput {
            seq,
            success: true,
            data: json!({}),
            aom_snapshot: vec![],
            timing: Timing {
                queue_ms: 0,
                exec_ms: 0,
            },
        })
    }

    fn surface_metadata(&self) -> ExecutionSurfaceMetadata {
        self.mac_policy.privileged_surface_metadata()
    }

    fn supports_eval(&self) -> bool {
        self.mac_policy.supports_pipe_action(&Action::Eval)
    }
}

fn parse_status_code(raw: &str) -> Result<i64, PipeError> {
    raw.trim()
        .parse::<i64>()
        .map_err(|_| PipeError::Protocol(format!("invalid browser status frame: {raw}")))
}

fn recv_status_frame(client: &dyn WsClient, timeout: Duration) -> Result<String, PipeError> {
    loop {
        let frame = client.recv_text_timeout(timeout)?;
        if is_ignorable_status_prelude(&frame) {
            continue;
        }
        return Ok(frame);
    }
}

fn is_ignorable_status_prelude(frame: &str) -> bool {
    let trimmed = frame.trim();
    if trimmed.starts_with("Welcome!") || trimmed.starts_with("Welcome ") {
        return true;
    }

    serde_json::from_str::<Value>(trimmed)
        .ok()
        .and_then(|value| value.get("type").and_then(Value::as_str).map(str::to_string))
        .is_some_and(|kind| kind == "welcome")
}
