mod common;

use std::io::{Read, Write};
use std::net::TcpListener;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
use std::thread;
use std::time::Duration;

use common::MockTransport;
use serde_json::{json, Value};
use sgclaw::agent::handle_browser_message;
use sgclaw::compat::runtime::execute_task;
use sgclaw::pipe::{Action, AgentMessage, BrowserMessage, BrowserPipeTool, Timing};
use sgclaw::security::MacPolicy;
use uuid::Uuid;

fn env_lock() -> &'static Mutex<()> {
    static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
    LOCK.get_or_init(|| Mutex::new(()))
}

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

fn temp_workspace_root() -> PathBuf {
    let root = std::env::temp_dir().join(format!("sgclaw-compat-runtime-{}", Uuid::new_v4()));
    std::fs::create_dir_all(&root).unwrap();
    root
}

fn start_fake_deepseek_server(
    responses: Vec<Value>,
) -> (String, Arc<Mutex<Vec<Value>>>, thread::JoinHandle<()>) {
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    listener.set_nonblocking(true).unwrap();
    let address = format!("http://{}", listener.local_addr().unwrap());
    let requests = Arc::new(Mutex::new(Vec::new()));
    let request_log = requests.clone();

    let handle = thread::spawn(move || {
        for response in responses {
            let deadline = std::time::Instant::now() + Duration::from_secs(5);
            let (mut stream, _) = loop {
                match listener.accept() {
                    Ok(pair) => break pair,
                    Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
                        assert!(
                            std::time::Instant::now() < deadline,
                            "timed out waiting for provider request"
                        );
                        thread::sleep(Duration::from_millis(10));
                    }
                    Err(err) => panic!("failed to accept provider request: {err}"),
                }
            };
            let body = read_http_json_body(&mut stream);
            request_log.lock().unwrap().push(body);

            let payload = response.to_string();
            let reply = format!(
                "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
                payload.as_bytes().len(),
                payload
            );
            stream.write_all(reply.as_bytes()).unwrap();
            stream.flush().unwrap();
        }
    });

    (address, requests, handle)
}

fn read_http_json_body(stream: &mut impl Read) -> Value {
    let mut buffer = Vec::new();
    let mut headers_end = None;

    while headers_end.is_none() {
        let mut chunk = [0_u8; 1024];
        let bytes = stream.read(&mut chunk).unwrap();
        assert!(bytes > 0, "unexpected EOF while reading headers");
        buffer.extend_from_slice(&chunk[..bytes]);
        headers_end = buffer.windows(4).position(|window| window == b"\r\n\r\n");
    }

    let headers_end = headers_end.unwrap() + 4;
    let headers = String::from_utf8(buffer[..headers_end].to_vec()).unwrap();
    let content_length = headers
        .lines()
        .find_map(|line| {
            let (name, value) = line.split_once(':')?;
            name.eq_ignore_ascii_case("content-length")
                .then(|| value.trim().parse::<usize>().unwrap())
        })
        .unwrap();

    while buffer.len() < headers_end + content_length {
        let mut chunk = vec![0_u8; content_length];
        let bytes = stream.read(&mut chunk).unwrap();
        assert!(bytes > 0, "unexpected EOF while reading body");
        buffer.extend_from_slice(&chunk[..bytes]);
    }

    serde_json::from_slice(&buffer[headers_end..headers_end + content_length]).unwrap()
}

#[test]
fn compat_runtime_uses_zeroclaw_provider_path_and_executes_browser_actions() {
    let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());

    let first_response = json!({
        "choices": [{
            "message": {
                "content": "",
                "tool_calls": [
                    {
                        "id": "call_1",
                        "type": "function",
                        "function": {
                            "name": "browser_action",
                            "arguments": serde_json::to_string(&json!({
                                "action": "navigate",
                                "expected_domain": "www.baidu.com",
                                "url": "https://www.baidu.com"
                            })).unwrap()
                        }
                    },
                    {
                        "id": "call_2",
                        "type": "function",
                        "function": {
                            "name": "browser_action",
                            "arguments": serde_json::to_string(&json!({
                                "action": "type",
                                "expected_domain": "www.baidu.com",
                                "selector": "#kw",
                                "text": "天气",
                                "clear_first": true
                            })).unwrap()
                        }
                    }
                ]
            }
        }],
        "usage": {
            "prompt_tokens": 12,
            "completion_tokens": 7
        }
    });
    let second_response = json!({
        "choices": [{
            "message": {
                "content": "已通过 ZeroClaw 执行任务: 打开百度搜索天气"
            }
        }],
        "usage": {
            "prompt_tokens": 15,
            "completion_tokens": 8
        }
    });
    let (base_url, requests, server_handle) =
        start_fake_deepseek_server(vec![first_response, second_response]);

    std::env::set_var("DEEPSEEK_API_KEY", "deepseek-test-key");
    std::env::set_var("DEEPSEEK_BASE_URL", base_url);
    std::env::set_var("DEEPSEEK_MODEL", "deepseek-chat");

    let workspace_root = temp_workspace_root();
    let transport = Arc::new(MockTransport::new(vec![
        BrowserMessage::Response {
            seq: 1,
            success: true,
            data: json!({ "navigated": true }),
            aom_snapshot: vec![],
            timing: Timing {
                queue_ms: 1,
                exec_ms: 10,
            },
        },
        BrowserMessage::Response {
            seq: 2,
            success: true,
            data: json!({ "typed": true }),
            aom_snapshot: vec![],
            timing: Timing {
                queue_ms: 1,
                exec_ms: 11,
            },
        },
    ]));
    let browser_tool = BrowserPipeTool::new(
        transport.clone(),
        test_policy(),
        vec![1, 2, 3, 4, 5, 6, 7, 8],
    )
    .with_response_timeout(Duration::from_secs(1));

    let summary = execute_task(
        transport.as_ref(),
        browser_tool,
        "打开百度搜索天气",
        &workspace_root,
    )
    .unwrap();
    server_handle.join().unwrap();

    let request_bodies = requests.lock().unwrap().clone();
    let sent = transport.sent_messages();

    assert_eq!(summary, "已通过 ZeroClaw 执行任务: 打开百度搜索天气");
    assert_eq!(request_bodies.len(), 2);
    assert_eq!(request_bodies[0]["model"], json!("deepseek-chat"));
    assert_eq!(
        request_bodies[0]["tools"][0]["function"]["name"],
        json!("browser_action")
    );
    assert!(request_bodies[1].to_string().contains("tool_call_id"));
    assert!(sent.iter().any(|message| {
        matches!(
            message,
            AgentMessage::LogEntry { level, message }
                if level == "info" && message == "navigate https://www.baidu.com"
        )
    }));
    assert!(sent.iter().any(|message| {
        matches!(
            message,
            AgentMessage::LogEntry { level, message }
                if level == "info" && message == "type 天气 into #kw"
        )
    }));
    assert!(sent.iter().any(|message| {
        matches!(
            message,
            AgentMessage::Command { action, .. } if action == &Action::Navigate
        )
    }));
    assert!(sent.iter().any(|message| {
        matches!(
            message,
            AgentMessage::Command { action, .. } if action == &Action::Type
        )
    }));
}

#[test]
fn handle_browser_message_uses_compat_runtime_summary_when_deepseek_env_is_set() {
    let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());

    let first_response = json!({
        "choices": [{
            "message": {
                "content": "",
                "tool_calls": [{
                    "id": "call_1",
                    "type": "function",
                    "function": {
                        "name": "browser_action",
                        "arguments": serde_json::to_string(&json!({
                            "action": "navigate",
                            "expected_domain": "www.baidu.com",
                            "url": "https://www.baidu.com"
                        })).unwrap()
                    }
                }]
            }
        }]
    });
    let second_response = json!({
        "choices": [{
            "message": {
                "content": "来自 ZeroClaw runtime"
            }
        }]
    });
    let (base_url, _, server_handle) = start_fake_deepseek_server(vec![first_response, second_response]);

    std::env::set_var("DEEPSEEK_API_KEY", "deepseek-test-key");
    std::env::set_var("DEEPSEEK_BASE_URL", base_url);
    std::env::set_var("DEEPSEEK_MODEL", "deepseek-chat");

    let workspace_root = temp_workspace_root();
    let original_dir = std::env::current_dir().unwrap();
    std::env::set_current_dir(&workspace_root).unwrap();

    let transport = Arc::new(MockTransport::new(vec![BrowserMessage::Response {
        seq: 1,
        success: true,
        data: json!({ "navigated": true }),
        aom_snapshot: vec![],
        timing: Timing {
            queue_ms: 1,
            exec_ms: 10,
        },
    }]));
    let browser_tool = BrowserPipeTool::new(
        transport.clone(),
        test_policy(),
        vec![1, 2, 3, 4, 5, 6, 7, 8],
    )
    .with_response_timeout(Duration::from_secs(1));

    handle_browser_message(
        transport.as_ref(),
        &browser_tool,
        BrowserMessage::SubmitTask {
            instruction: "打开百度搜索天气".to_string(),
        },
    )
    .unwrap();
    server_handle.join().unwrap();
    std::env::set_current_dir(original_dir).unwrap();

    let sent = transport.sent_messages();

    assert!(sent.iter().any(|message| {
        matches!(
            message,
            AgentMessage::TaskComplete { success, summary }
                if *success && summary == "来自 ZeroClaw runtime"
        )
    }));
}
