use alcomd3_mcp_protocol::{
    ENDPOINT_FILE_ENV, EndpointMetadata, IPC_PROTOCOL_VERSION, IpcTransport,
};
use serde_json::Value;
use std::fs;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::process::{ChildStdin, ChildStdout, Command, Stdio};
use uuid::Uuid;

#[test]
fn bridge_stays_connected_when_gui_endpoint_disappears() {
    let test_dir =
        std::env::temp_dir().join(format!("alcomd3-mcp-lifecycle-{}", Uuid::new_v4().simple()));
    fs::create_dir_all(&test_dir).unwrap();

    let endpoint_file = test_dir.join("endpoint.json");
    let metadata = EndpointMetadata {
        protocol_version: IPC_PROTOCOL_VERSION,
        transport: IpcTransport::Tcp,
        host: "127.0.0.1".to_string(),
        port: 9,
        token: "test-token".to_string(),
        pid: std::process::id(),
    };
    fs::write(&endpoint_file, serde_json::to_vec(&metadata).unwrap()).unwrap();

    let mut bridge = Command::new(bridge_exe())
        .env(ENDPOINT_FILE_ENV, &endpoint_file)
        .env(
            "ALCOMD3_GUI_EXECUTABLE",
            test_dir.join(gui_executable_name()),
        )
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()
        .unwrap();

    let mut stdin = bridge.stdin.take().unwrap();
    let mut stdout = BufReader::new(bridge.stdout.take().unwrap());

    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"lifecycle-test","version":"0.0.0"}}}"#,
    );
    assert_eq!(read_response(&mut stdout)["id"], 1);

    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#,
    );

    fs::remove_file(&endpoint_file).unwrap();

    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"alcomd3_list_projects","arguments":{}}}"#,
    );
    let response = read_response(&mut stdout);
    assert_eq!(response["id"], 2);
    assert_eq!(
        response["result"]["structuredContent"]["error"]["code"],
        "alcomd3_unavailable"
    );
    assert!(
        bridge.try_wait().unwrap().is_none(),
        "bridge should stay alive after the GUI endpoint disappears"
    );

    drop(stdin);
    bridge.kill().ok();
    bridge.wait().ok();
    fs::remove_dir_all(test_dir).ok();
}

#[test]
fn bridge_attempts_to_start_gui_when_endpoint_is_missing() {
    let test_dir =
        std::env::temp_dir().join(format!("alcomd3-mcp-autostart-{}", Uuid::new_v4().simple()));
    fs::create_dir_all(&test_dir).unwrap();

    let endpoint_file = test_dir.join("endpoint.json");
    let missing_gui = test_dir.join(gui_executable_name());

    let mut bridge = Command::new(bridge_exe())
        .env(ENDPOINT_FILE_ENV, &endpoint_file)
        .env("ALCOMD3_GUI_EXECUTABLE", &missing_gui)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()
        .unwrap();

    let mut stdin = bridge.stdin.take().unwrap();
    let mut stdout = BufReader::new(bridge.stdout.take().unwrap());

    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"autostart-test","version":"0.0.0"}}}"#,
    );
    assert_eq!(read_response(&mut stdout)["id"], 1);

    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#,
    );
    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"alcomd3_list_projects","arguments":{}}}"#,
    );

    let response = read_response(&mut stdout);
    assert_eq!(response["id"], 2);
    let message = response["result"]["structuredContent"]["error"]["message"]
        .as_str()
        .unwrap();
    assert!(
        message.contains("starting ALCOMD3 GUI"),
        "expected auto-start failure context, got: {message}"
    );
    assert!(
        bridge.try_wait().unwrap().is_none(),
        "bridge should stay alive after GUI auto-start fails"
    );

    drop(stdin);
    bridge.kill().ok();
    bridge.wait().ok();
    fs::remove_dir_all(test_dir).ok();
}

#[test]
fn bridge_does_not_start_gui_for_protocol_mismatch() {
    let test_dir =
        std::env::temp_dir().join(format!("alcomd3-mcp-protocol-{}", Uuid::new_v4().simple()));
    fs::create_dir_all(&test_dir).unwrap();

    let endpoint_file = test_dir.join("endpoint.json");
    let metadata = EndpointMetadata {
        protocol_version: IPC_PROTOCOL_VERSION + 1,
        transport: IpcTransport::Tcp,
        host: "127.0.0.1".to_string(),
        port: 9,
        token: "test-token".to_string(),
        pid: std::process::id(),
    };
    fs::write(&endpoint_file, serde_json::to_vec(&metadata).unwrap()).unwrap();

    let mut bridge = Command::new(bridge_exe())
        .env(ENDPOINT_FILE_ENV, &endpoint_file)
        .env(
            "ALCOMD3_GUI_EXECUTABLE",
            test_dir.join(gui_executable_name()),
        )
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()
        .unwrap();

    let mut stdin = bridge.stdin.take().unwrap();
    let mut stdout = BufReader::new(bridge.stdout.take().unwrap());

    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"protocol-test","version":"0.0.0"}}}"#,
    );
    assert_eq!(read_response(&mut stdout)["id"], 1);

    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#,
    );
    write_message(
        &mut stdin,
        r#"{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"alcomd3_list_projects","arguments":{}}}"#,
    );

    let response = read_response(&mut stdout);
    assert_eq!(response["id"], 2);
    let message = response["result"]["structuredContent"]["error"]["message"]
        .as_str()
        .unwrap();
    assert!(
        message.contains("protocol mismatch"),
        "expected protocol mismatch context, got: {message}"
    );
    assert!(
        !message.contains("starting ALCOMD3 GUI"),
        "protocol mismatch must not trigger GUI auto-start: {message}"
    );
    assert!(
        bridge.try_wait().unwrap().is_none(),
        "bridge should stay alive after a protocol mismatch"
    );

    drop(stdin);
    bridge.kill().ok();
    bridge.wait().ok();
    fs::remove_dir_all(test_dir).ok();
}

fn write_message(stdin: &mut ChildStdin, message: &str) {
    writeln!(stdin, "{message}").unwrap();
    stdin.flush().unwrap();
}

fn read_response(stdout: &mut BufReader<ChildStdout>) -> Value {
    let mut line = String::new();
    stdout.read_line(&mut line).unwrap();
    serde_json::from_str(&line).unwrap()
}

fn bridge_exe() -> &'static str {
    env!("CARGO_BIN_EXE_alcomd3-mcp")
}

fn gui_executable_name() -> &'static Path {
    #[cfg(windows)]
    {
        Path::new("ALCOM.exe")
    }
    #[cfg(not(windows))]
    {
        Path::new("ALCOM")
    }
}
