use std::io::{Cursor, Result as IoResult, Write};
use std::sync::{Arc, Mutex};
use std::time::Duration;

use sgclaw::pipe::{
    perform_handshake, Action, AgentMessage, BrowserPipeTool, StdioTransport, Timing,
};
use sgclaw::security::MacPolicy;

#[derive(Clone, Default)]
struct SharedBuffer {
    inner: Arc<Mutex<Vec<u8>>>,
}

impl SharedBuffer {
    fn snapshot(&self) -> String {
        String::from_utf8(self.inner.lock().unwrap().clone()).unwrap()
    }
}

impl Write for SharedBuffer {
    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
        self.inner.lock().unwrap().extend_from_slice(buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> IoResult<()> {
        Ok(())
    }
}

#[test]
fn handshake_and_command_flow_work_over_json_line_transport() {
    let reader = Cursor::new(
        concat!(
            r#"{"type":"init","version":"1.0","hmac_seed":"0123456789abcdef","capabilities":["browser_action"]}"#,
            "\n",
            r#"{"type":"response","seq":1,"success":true,"data":{"text":"提交成功"},"aom_snapshot":[],"timing":{"queue_ms":2,"exec_ms":38}}"#,
            "\n"
        )
        .as_bytes()
        .to_vec(),
    );
    let writer = SharedBuffer::default();
    let captured = writer.clone();
    let transport = Arc::new(StdioTransport::new(reader, writer));

    let handshake = perform_handshake(transport.as_ref(), Duration::from_secs(1)).unwrap();
    let policy = MacPolicy::from_json_str(
        r#"{
            "version": "1.0",
            "domains": { "allowed": ["oa.example.com"] },
            "pipe_actions": {
                "allowed": ["click", "type", "navigate", "getText"],
                "blocked": []
            }
        }"#,
    )
    .unwrap();
    let tool = BrowserPipeTool::new(transport, policy, handshake.session_key)
        .with_response_timeout(Duration::from_secs(1));

    let result = tool
        .invoke(
            Action::Click,
            serde_json::json!({ "selector": "#submit" }),
            "oa.example.com",
        )
        .unwrap();
    let written = captured.snapshot();

    assert_eq!(
        result.timing,
        Timing {
            queue_ms: 2,
            exec_ms: 38
        }
    );
    assert!(written.contains(r#""type":"init_ack""#));
    assert!(written.contains(r#""type":"command""#));
    assert!(written.contains(r#""action":"click""#));

    let lines: Vec<&str> = written.lines().collect();
    let command: AgentMessage = serde_json::from_str(lines[1]).unwrap();
    assert!(matches!(
        command,
        AgentMessage::Command { seq, .. } if seq == 1
    ));
}
