use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use serde_json::{json, Value};
use sgclaw::browser::ws_backend::WsClient;
use sgclaw::browser::{BrowserBackend, WsBrowserBackend};
use sgclaw::pipe::{Action, PipeError};
use sgclaw::security::MacPolicy;

fn test_policy() -> MacPolicy {
    MacPolicy::from_json_str(
        r#"{
            "version": "1.0",
            "domains": { "allowed": ["www.baidu.com"] },
            "pipe_actions": {
                "allowed": ["click", "type", "navigate", "getText", "eval"],
                "blocked": []
            }
        }"#,
    )
    .unwrap()
}

struct FakeWsClient {
    incoming: Mutex<VecDeque<Result<String, PipeError>>>,
    sent: Mutex<Vec<String>>,
}

impl FakeWsClient {
    fn new(frames: Vec<Result<&str, PipeError>>) -> Self {
        Self {
            incoming: Mutex::new(
                frames
                    .into_iter()
                    .map(|frame| frame.map(str::to_string))
                    .collect(),
            ),
            sent: Mutex::new(Vec::new()),
        }
    }

    fn sent_frames(&self) -> Vec<String> {
        self.sent.lock().unwrap().clone()
    }
}

impl WsClient for FakeWsClient {
    fn send_text(&self, payload: &str) -> Result<(), PipeError> {
        self.sent.lock().unwrap().push(payload.to_string());
        Ok(())
    }

    fn recv_text_timeout(&self, _timeout: Duration) -> Result<String, PipeError> {
        self.incoming
            .lock()
            .unwrap()
            .pop_front()
            .unwrap_or(Err(PipeError::Timeout))
    }
}

#[test]
fn ws_backend_ignores_welcome_frame_before_zero_status() {
    let client = Arc::new(FakeWsClient::new(vec![
        Ok("Welcome! You are client #1"),
        Ok("0"),
        Ok(
            r#"["https://www.baidu.com/current","callBackJsToCpp","https://www.baidu.com/current@_@https://www.baidu.com@_@sgclaw_cb_1@_@sgHideBrowserCallAfterLoaded@_@"]"#,
        ),
    ]));
    let backend = WsBrowserBackend::new(
        client.clone(),
        test_policy(),
        "https://www.baidu.com/current",
    )
    .with_response_timeout(Duration::from_secs(1));

    let output = backend
        .invoke(
            Action::Navigate,
            json!({ "url": "https://www.baidu.com" }),
            "www.baidu.com",
        )
        .unwrap();

    assert!(output.success);
    let sent = client.sent_frames();
    assert_eq!(sent.len(), 1);
}

#[test]
fn ws_backend_ignores_json_welcome_frame_before_zero_status() {
    let client = Arc::new(FakeWsClient::new(vec![
        Ok(r#"{"type":"welcome","client_id":17,"server_time":"2026-04-04T11:04:54"}"#),
        Ok("0"),
        Ok(
            r#"["https://www.baidu.com/current","callBackJsToCpp","https://www.baidu.com/current@_@https://www.baidu.com@_@sgclaw_cb_1@_@sgHideBrowserCallAfterLoaded@_@"]"#,
        ),
    ]));
    let backend = WsBrowserBackend::new(
        client.clone(),
        test_policy(),
        "https://www.baidu.com/current",
    )
    .with_response_timeout(Duration::from_secs(1));

    let output = backend
        .invoke(
            Action::Navigate,
            json!({ "url": "https://www.baidu.com" }),
            "www.baidu.com",
        )
        .unwrap();

    assert!(output.success);
    let sent = client.sent_frames();
    assert_eq!(sent.len(), 1);
}

#[test]
fn ws_backend_fails_on_non_numeric_non_welcome_status_frame() {
    let client = Arc::new(FakeWsClient::new(vec![Ok("not-a-status") ]));
    let backend = WsBrowserBackend::new(
        client,
        test_policy(),
        "https://www.baidu.com/current",
    )
    .with_response_timeout(Duration::from_secs(1));

    let error = backend
        .invoke(
            Action::Click,
            json!({
                "target_url": "https://www.baidu.com/current",
                "selector": "#submit"
            }),
            "www.baidu.com",
        )
        .unwrap_err();

    assert!(error.to_string().contains("invalid browser status frame: not-a-status"));
}
#[test]
fn ws_backend_returns_success_for_zero_without_callback() {
    let client = Arc::new(FakeWsClient::new(vec![
        Ok("0"),
        Ok(
            r#"["https://www.baidu.com/current","callBackJsToCpp","https://www.baidu.com/current@_@https://www.baidu.com@_@sgclaw_cb_1@_@sgHideBrowserCallAfterLoaded@_@"]"#,
        ),
    ]));
    let backend = WsBrowserBackend::new(
        client.clone(),
        test_policy(),
        "https://www.baidu.com/current",
    )
    .with_response_timeout(Duration::from_secs(1));

    let output = backend
        .invoke(
            Action::Navigate,
            json!({ "url": "https://www.baidu.com" }),
            "www.baidu.com",
        )
        .unwrap();

    assert_eq!(output.seq, 1);
    assert!(output.success);
    assert_eq!(output.data, json!({ "text": "" }));
    assert!(output.aom_snapshot.is_empty());

    let sent = client.sent_frames();
    assert_eq!(sent.len(), 1);
    let payload: Value = serde_json::from_str(&sent[0]).unwrap();
    assert_eq!(payload[1], json!("sgHideBrowserCallAfterLoaded"));
    assert_eq!(payload[2], json!("https://www.baidu.com"));
}

#[test]
fn ws_backend_fails_immediately_on_non_zero_return_code() {
    let client = Arc::new(FakeWsClient::new(vec![Ok("7")]));
    let backend = WsBrowserBackend::new(
        client,
        test_policy(),
        "https://www.baidu.com/current",
    )
    .with_response_timeout(Duration::from_secs(1));

    let error = backend
        .invoke(
            Action::Click,
            json!({
                "target_url": "https://www.baidu.com/current",
                "selector": "#submit"
            }),
            "www.baidu.com",
        )
        .unwrap_err();

    assert!(error.to_string().contains("browser returned non-zero status: 7"));
}

#[test]
fn ws_backend_waits_for_callback_and_normalizes_result_payload() {
    let client = Arc::new(FakeWsClient::new(vec![
        Ok("0"),
        Ok(
            r#"["https://www.baidu.com/current","callBackJsToCpp","https://www.baidu.com/current@_@https://www.baidu.com/current@_@sgclaw_cb_1@_@sgBrowserExcuteJsCodeByArea@_@天气"]"#,
        ),
    ]));
    let backend = WsBrowserBackend::new(
        client.clone(),
        test_policy(),
        "https://www.baidu.com/current",
    )
    .with_response_timeout(Duration::from_secs(1));

    let output = backend
        .invoke(
            Action::GetText,
            json!({
                "target_url": "https://www.baidu.com/current",
                "selector": "#content"
            }),
            "www.baidu.com",
        )
        .unwrap();

    assert_eq!(output.seq, 1);
    assert!(output.success);
    assert_eq!(output.data, json!({ "text": "天气" }));
    assert!(output.aom_snapshot.is_empty());

    let sent = client.sent_frames();
    assert_eq!(sent.len(), 1);
    let payload: Value = serde_json::from_str(&sent[0]).unwrap();
    assert_eq!(payload[1], json!("sgBrowserExcuteJsCodeByArea"));
}

#[test]
fn ws_backend_times_out_while_waiting_for_callback_after_zero_status() {
    let client = Arc::new(FakeWsClient::new(vec![Ok("0")]));
    let backend = WsBrowserBackend::new(
        client,
        test_policy(),
        "https://www.baidu.com/current",
    )
    .with_response_timeout(Duration::from_millis(1));

    let error = backend
        .invoke(
            Action::Eval,
            json!({
                "target_url": "https://www.baidu.com/current",
                "script": "2 + 2"
            }),
            "www.baidu.com",
        )
        .unwrap_err();

    assert!(matches!(error, PipeError::Timeout));
}

#[test]
fn ws_backend_times_out_when_navigate_callback_never_arrives() {
    let client = Arc::new(FakeWsClient::new(vec![
        Err(PipeError::Timeout),
        Err(PipeError::Timeout),
    ]));
    let backend = WsBrowserBackend::new(client.clone(), test_policy(), "https://www.zhihu.com")
        .with_response_timeout(Duration::from_millis(1));

    let error = backend
        .invoke(
            Action::Navigate,
            json!({ "url": "https://www.zhihu.com/hot" }),
            "www.baidu.com",
        )
        .unwrap_err();

    assert!(matches!(error, PipeError::Timeout));
    let sent = client.sent_frames();
    let payload: Value = serde_json::from_str(&sent[0]).unwrap();
    assert_eq!(payload[1], json!("sgHideBrowserCallAfterLoaded"));
    assert_eq!(payload[2], json!("https://www.zhihu.com/hot"));
}

#[test]
fn ws_backend_reuses_last_navigated_url_for_followup_requests() {
    let client = Arc::new(FakeWsClient::new(vec![
        Ok("0"),
        Ok(
            r#"["https://www.baidu.com/current","callBackJsToCpp","https://www.baidu.com/current@_@https://www.baidu.com@_@sgclaw_cb_1@_@sgHideBrowserCallAfterLoaded@_@"]"#,
        ),
        Ok("0"),
        Ok(
            r#"["https://www.zhihu.com/hot","callBackJsToCpp","https://www.zhihu.com/hot@_@https://www.zhihu.com/hot@_@sgclaw_cb_2@_@sgBrowserExcuteJsCodeByArea@_@热榜文本"]"#,
        ),
    ]));
    let backend = WsBrowserBackend::new(client.clone(), test_policy(), "about:blank")
        .with_response_timeout(Duration::from_secs(1));

    backend
        .invoke(
            Action::Navigate,
            json!({ "url": "https://www.zhihu.com/hot" }),
            "www.baidu.com",
        )
        .unwrap();

    let output = backend
        .invoke(
            Action::GetText,
            json!({ "selector": "body" }),
            "www.baidu.com",
        )
        .unwrap();

    assert!(output.success);
    assert_eq!(output.data, json!({ "text": "热榜文本" }));

    let sent = client.sent_frames();
    assert_eq!(sent.len(), 2);

    let navigate_payload: Value = serde_json::from_str(&sent[0]).unwrap();
    assert_eq!(navigate_payload[0], json!("about:blank"));
    assert_eq!(navigate_payload[1], json!("sgHideBrowserCallAfterLoaded"));
    assert_eq!(navigate_payload[2], json!("https://www.zhihu.com/hot"));

    let followup_payload: Value = serde_json::from_str(&sent[1]).unwrap();
    assert_eq!(followup_payload[0], json!("https://www.zhihu.com/hot"));
    assert_eq!(followup_payload[1], json!("sgBrowserExcuteJsCodeByArea"));
    assert_eq!(followup_payload[2], json!("https://www.zhihu.com/hot"));
    assert_eq!(followup_payload[4], json!("hide"));
}

#[test]
fn ws_backend_propagates_socket_drop_after_navigate_send() {
    let client = Arc::new(FakeWsClient::new(vec![Err(PipeError::PipeClosed)]));
    let backend = WsBrowserBackend::new(
        client,
        test_policy(),
        "https://www.baidu.com/current",
    )
    .with_response_timeout(Duration::from_secs(1));

    let error = backend
        .invoke(
            Action::Navigate,
            json!({ "url": "https://www.baidu.com" }),
            "www.baidu.com",
        )
        .unwrap_err();

    assert!(matches!(error, PipeError::PipeClosed));
}
