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

use serde_json::json;
use sgclaw::browser::bridge_contract::{
    BridgeBrowserActionError, BridgeBrowserActionReply, BridgeBrowserActionRequest,
    BridgeBrowserActionSuccess,
};
use sgclaw::browser::bridge_transport::BridgeActionTransport;
use sgclaw::browser::{BridgeBrowserBackend, BrowserBackend};
use sgclaw::pipe::{Action, PipeError, Timing};
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 FakeBridgeTransport {
    requests: Mutex<Vec<BridgeBrowserActionRequest>>,
    replies: Mutex<VecDeque<Result<BridgeBrowserActionReply, PipeError>>>,
}

impl FakeBridgeTransport {
    fn new(replies: Vec<Result<BridgeBrowserActionReply, PipeError>>) -> Self {
        Self {
            requests: Mutex::new(Vec::new()),
            replies: Mutex::new(replies.into()),
        }
    }

    fn recorded_requests(&self) -> Vec<BridgeBrowserActionRequest> {
        self.requests.lock().unwrap().clone()
    }
}

impl BridgeActionTransport for FakeBridgeTransport {
    fn execute(
        &self,
        request: BridgeBrowserActionRequest,
    ) -> Result<BridgeBrowserActionReply, PipeError> {
        self.requests.lock().unwrap().push(request);
        self.replies
            .lock()
            .unwrap()
            .pop_front()
            .unwrap_or(Err(PipeError::Timeout))
    }
}

#[test]
fn bridge_backend_maps_navigate_to_bridge_action_request() {
    let transport = Arc::new(FakeBridgeTransport::new(vec![Ok(
        BridgeBrowserActionReply::Success(BridgeBrowserActionSuccess {
            data: json!({ "navigated": true }),
            aom_snapshot: vec![],
            timing: Timing {
                queue_ms: 1,
                exec_ms: 11,
            },
        }),
    )]));
    let backend = BridgeBrowserBackend::new(transport.clone(), test_policy());

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

    assert_eq!(
        transport.recorded_requests(),
        vec![BridgeBrowserActionRequest::new(
            "navigate",
            json!({ "url": "https://www.baidu.com" }),
            "www.baidu.com",
        )]
    );
    assert_eq!(output.seq, 1);
    assert!(output.success);
}

#[test]
fn bridge_backend_normalizes_successful_bridge_reply() {
    let transport = Arc::new(FakeBridgeTransport::new(vec![Ok(
        BridgeBrowserActionReply::Success(BridgeBrowserActionSuccess {
            data: json!({ "text": "天气" }),
            aom_snapshot: vec![json!({ "role": "textbox", "name": "百度一下" })],
            timing: Timing {
                queue_ms: 4,
                exec_ms: 14,
            },
        }),
    )]));
    let backend = BridgeBrowserBackend::new(transport, test_policy());

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

    assert_eq!(output.seq, 1);
    assert!(output.success);
    assert_eq!(output.data, json!({ "text": "天气" }));
    assert_eq!(
        output.aom_snapshot,
        vec![json!({ "role": "textbox", "name": "百度一下" })]
    );
    assert_eq!(
        output.timing,
        Timing {
            queue_ms: 4,
            exec_ms: 14,
        }
    );
}

#[test]
fn bridge_backend_maps_bridge_failure_to_pipe_error() {
    let transport = Arc::new(FakeBridgeTransport::new(vec![Ok(
        BridgeBrowserActionReply::Error(BridgeBrowserActionError {
            message: "selector not found".to_string(),
            details: json!({ "selector": "#missing" }),
        }),
    )]));
    let backend = BridgeBrowserBackend::new(transport, test_policy());

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

    assert!(matches!(error, PipeError::Protocol(message) if message == "bridge action failed: selector not found"));
}
