mod common;

use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Duration;

use common::MockTransport;
use serde_json::Value;
use sgclaw::agent::{run_submit_task, AgentEventSink, AgentRuntimeContext, SubmitTaskRequest};
use sgclaw::agent::task_runner::run_submit_task_with_browser_backend;
use sgclaw::browser::BrowserBackend;
use sgclaw::pipe::{
    Action, AgentMessage, BrowserMessage, BrowserPipeTool, CommandOutput, ConversationMessage,
    ExecutionSurfaceMetadata, PipeError, 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": ["oa.example.com", "www.baidu.com"] },
            "pipe_actions": {
                "allowed": ["click", "type", "navigate", "getText"],
                "blocked": ["eval", "executeJsInPage"]
            }
        }"#,
    )
    .unwrap()
}

fn test_browser_tool(transport: Arc<MockTransport>) -> BrowserPipeTool<MockTransport> {
    BrowserPipeTool::new(
        transport,
        test_policy(),
        vec![1, 2, 3, 4, 5, 6, 7, 8],
    )
    .with_response_timeout(Duration::from_secs(1))
}

#[derive(Clone, Default)]
struct StubBrowserBackend;

impl BrowserBackend for StubBrowserBackend {
    fn invoke(
        &self,
        _action: Action,
        _params: Value,
        _expected_domain: &str,
    ) -> Result<CommandOutput, PipeError> {
        Err(PipeError::Protocol(
            "stub backend should not be invoked in this test".to_string(),
        ))
    }

    fn surface_metadata(&self) -> ExecutionSurfaceMetadata {
        ExecutionSurfaceMetadata::privileged_browser_pipe("stub-backend")
    }
}

#[derive(Default)]
struct RecordingSink {
    sent: Mutex<Vec<AgentMessage>>,
}

impl RecordingSink {
    fn sent_messages(&self) -> Vec<AgentMessage> {
        self.sent.lock().unwrap().clone()
    }
}

impl AgentEventSink for RecordingSink {
    fn send(&self, message: &AgentMessage) -> Result<(), PipeError> {
        self.sent.lock().unwrap().push(message.clone());
        Ok(())
    }
}

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

#[test]
fn run_submit_task_with_browser_backend_accepts_ws_only_backend_and_preserves_existing_entry() {
    let _existing_entry: fn(
        &MockTransport,
        &dyn AgentEventSink,
        &BrowserPipeTool<MockTransport>,
        &AgentRuntimeContext,
        SubmitTaskRequest,
    ) -> Result<(), PipeError> = run_submit_task::<MockTransport>;
    let _ws_only_entry: fn(
        &MockTransport,
        &dyn AgentEventSink,
        Arc<dyn BrowserBackend>,
        &AgentRuntimeContext,
        SubmitTaskRequest,
    ) -> Result<(), PipeError> = run_submit_task_with_browser_backend::<MockTransport>;

    let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
    std::env::remove_var("DEEPSEEK_API_KEY");
    std::env::remove_var("DEEPSEEK_BASE_URL");
    std::env::remove_var("DEEPSEEK_MODEL");

    let transport = Arc::new(MockTransport::new(vec![]));
    let sink = RecordingSink::default();
    let backend: Arc<dyn BrowserBackend> = Arc::new(StubBrowserBackend);

    run_submit_task_with_browser_backend(
        transport.as_ref(),
        &sink,
        backend,
        &AgentRuntimeContext::default(),
        SubmitTaskRequest {
            instruction: "打开百度搜索天气".to_string(),
            ..SubmitTaskRequest::default()
        },
    )
    .unwrap();

    let sent = sink.sent_messages();
    assert!(transport.sent_messages().is_empty());
    assert_eq!(sent.len(), 2);
    assert!(matches!(&sent[0], AgentMessage::LogEntry { level, .. } if level == "info"));
    assert!(matches!(
        &sent[1],
        AgentMessage::TaskComplete { success, summary }
            if !success && summary.contains("未配置大语言模型")
    ));
}

#[test]
fn run_submit_task_rejects_blank_instruction_without_emitting_logs() {
    let transport = Arc::new(MockTransport::new(vec![]));
    let browser_tool = test_browser_tool(transport.clone());

    run_submit_task(
        transport.as_ref(),
        transport.as_ref(),
        &browser_tool,
        &AgentRuntimeContext::default(),
        SubmitTaskRequest {
            instruction: "   ".to_string(),
            ..SubmitTaskRequest::default()
        },
    )
    .unwrap();

    let sent = transport.sent_messages();
    assert_eq!(sent.len(), 1);
    assert_eq!(
        sent[0],
        AgentMessage::TaskComplete {
            success: false,
            summary: "请输入任务内容。".to_string(),
        }
    );
}

#[test]
fn run_submit_task_can_emit_to_custom_sink() {
    let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
    std::env::remove_var("DEEPSEEK_API_KEY");
    std::env::remove_var("DEEPSEEK_BASE_URL");
    std::env::remove_var("DEEPSEEK_MODEL");

    let transport = Arc::new(MockTransport::new(vec![]));
    let browser_tool = test_browser_tool(transport.clone());
    let sink = RecordingSink::default();

    run_submit_task(
        transport.as_ref(),
        &sink,
        &browser_tool,
        &AgentRuntimeContext::default(),
        SubmitTaskRequest {
            instruction: "打开百度搜索天气".to_string(),
            messages: vec![ConversationMessage {
                role: "user".to_string(),
                content: "上一轮问题".to_string(),
            }],
            ..SubmitTaskRequest::default()
        },
    )
    .unwrap();

    let sent = sink.sent_messages();
    assert!(transport.sent_messages().is_empty());
    assert_eq!(sent.len(), 3);
    assert!(matches!(
        &sent[0],
        AgentMessage::LogEntry { level, message }
            if level == "info"
                && message
                    == &format!(
                        "sgclaw runtime version={} protocol={}",
                        env!("CARGO_PKG_VERSION"),
                        sgclaw::pipe::protocol::PROTOCOL_VERSION
                    )
    ));
    assert!(matches!(
        &sent[1],
        AgentMessage::LogEntry { level, message }
            if level == "info" && message == "continuing conversation with 1 prior turns"
    ));
    assert!(matches!(
        &sent[2],
        AgentMessage::TaskComplete { success, summary }
            if !success && summary.contains("未配置大语言模型")
    ));
}

#[test]
fn run_submit_task_without_llm_config_emits_runtime_version_then_failure() {
    let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
    std::env::remove_var("DEEPSEEK_API_KEY");
    std::env::remove_var("DEEPSEEK_BASE_URL");
    std::env::remove_var("DEEPSEEK_MODEL");

    let transport = Arc::new(MockTransport::new(vec![]));
    let browser_tool = test_browser_tool(transport.clone());

    run_submit_task(
        transport.as_ref(),
        transport.as_ref(),
        &browser_tool,
        &AgentRuntimeContext::default(),
        SubmitTaskRequest {
            instruction: "打开百度搜索天气".to_string(),
            ..SubmitTaskRequest::default()
        },
    )
    .unwrap();

    let sent = transport.sent_messages();
    assert_eq!(sent.len(), 2);
    assert!(matches!(
        &sent[0],
        AgentMessage::LogEntry { level, message }
            if level == "info"
                && message
                    == &format!(
                        "sgclaw runtime version={} protocol={}",
                        env!("CARGO_PKG_VERSION"),
                        sgclaw::pipe::protocol::PROTOCOL_VERSION
                    )
    ));
    assert!(matches!(
        &sent[1],
        AgentMessage::TaskComplete { success, summary }
            if !success && summary.contains("未配置大语言模型")
    ));
}

#[test]
fn run_submit_task_logs_prior_turn_count_before_completion() {
    let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
    std::env::remove_var("DEEPSEEK_API_KEY");
    std::env::remove_var("DEEPSEEK_BASE_URL");
    std::env::remove_var("DEEPSEEK_MODEL");

    let transport = Arc::new(MockTransport::new(vec![]));
    let browser_tool = test_browser_tool(transport.clone());

    run_submit_task(
        transport.as_ref(),
        transport.as_ref(),
        &browser_tool,
        &AgentRuntimeContext::default(),
        SubmitTaskRequest {
            instruction: "继续处理当前页面".to_string(),
            messages: vec![
                ConversationMessage {
                    role: "user".to_string(),
                    content: "上一轮问题".to_string(),
                },
                ConversationMessage {
                    role: "assistant".to_string(),
                    content: "上一轮回答".to_string(),
                },
            ],
            ..SubmitTaskRequest::default()
        },
    )
    .unwrap();

    let sent = transport.sent_messages();
    assert_eq!(sent.len(), 3);
    assert!(matches!(
        &sent[0],
        AgentMessage::LogEntry { level, message }
            if level == "info"
                && message
                    == &format!(
                        "sgclaw runtime version={} protocol={}",
                        env!("CARGO_PKG_VERSION"),
                        sgclaw::pipe::protocol::PROTOCOL_VERSION
                    )
    ));
    assert!(matches!(
        &sent[1],
        AgentMessage::LogEntry { level, message }
            if level == "info" && message == "continuing conversation with 2 prior turns"
    ));
    assert!(matches!(
        &sent[2],
        AgentMessage::TaskComplete { success, summary }
            if !success && summary.contains("未配置大语言模型")
    ));
}

#[test]
fn run_submit_task_reports_settings_load_error_and_final_failure() {
    let workspace_root = temp_workspace_root();
    let config_path = workspace_root.join("sgclaw_config.json");
    fs::write(&config_path, "{").unwrap();

    let transport = Arc::new(MockTransport::new(vec![]));
    let browser_tool = test_browser_tool(transport.clone());
    let context = AgentRuntimeContext::new(Some(config_path), workspace_root);

    run_submit_task(
        transport.as_ref(),
        transport.as_ref(),
        &browser_tool,
        &context,
        SubmitTaskRequest {
            instruction: "打开百度".to_string(),
            ..SubmitTaskRequest::default()
        },
    )
    .unwrap();

    let sent = transport.sent_messages();
    assert_eq!(sent.len(), 3);
    assert!(matches!(
        &sent[0],
        AgentMessage::LogEntry { level, message }
            if level == "info"
                && message
                    == &format!(
                        "sgclaw runtime version={} protocol={}",
                        env!("CARGO_PKG_VERSION"),
                        sgclaw::pipe::protocol::PROTOCOL_VERSION
                    )
    ));
    assert!(matches!(
        &sent[1],
        AgentMessage::LogEntry { level, message }
            if level == "error" && message.starts_with("failed to load DeepSeek config:")
    ));
    assert!(matches!(
        &sent[2],
        AgentMessage::TaskComplete { success, summary }
            if !success && summary.contains("invalid DeepSeek config JSON")
    ));
}

#[test]
fn handle_browser_message_normalizes_empty_optional_submit_fields() {
    let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
    std::env::remove_var("DEEPSEEK_API_KEY");
    std::env::remove_var("DEEPSEEK_BASE_URL");
    std::env::remove_var("DEEPSEEK_MODEL");

    let transport = Arc::new(MockTransport::new(vec![]));
    let browser_tool = test_browser_tool(transport.clone());

    sgclaw::agent::handle_browser_message(
        transport.as_ref(),
        &browser_tool,
        BrowserMessage::SubmitTask {
            instruction: "打开百度".to_string(),
            conversation_id: "   ".to_string(),
            messages: vec![],
            page_url: "".to_string(),
            page_title: "\n\t".to_string(),
        },
    )
    .unwrap();

    let sent = transport.sent_messages();
    assert_eq!(sent.len(), 2);
    assert!(matches!(&sent[0], AgentMessage::LogEntry { .. }));
    assert!(matches!(
        &sent[1],
        AgentMessage::TaskComplete { success, summary }
            if !success && summary.contains("未配置大语言模型")
    ));
}

#[test]
fn handle_browser_message_emits_status_for_lifecycle_messages() {
    let transport = Arc::new(MockTransport::new(vec![]));
    let browser_tool = test_browser_tool(transport.clone());

    sgclaw::agent::handle_browser_message(
        transport.as_ref(),
        &browser_tool,
        BrowserMessage::Connect,
    )
    .unwrap();
    sgclaw::agent::handle_browser_message(
        transport.as_ref(),
        &browser_tool,
        BrowserMessage::Start,
    )
    .unwrap();
    sgclaw::agent::handle_browser_message(
        transport.as_ref(),
        &browser_tool,
        BrowserMessage::Stop,
    )
    .unwrap();

    assert_eq!(
        transport.sent_messages(),
        vec![
            AgentMessage::StatusChanged {
                state: "connected".to_string(),
            },
            AgentMessage::StatusChanged {
                state: "started".to_string(),
            },
            AgentMessage::StatusChanged {
                state: "stopped".to_string(),
            },
        ]
    );
}

#[test]
fn handle_browser_message_still_ignores_init_and_unsolicited_response() {
    let transport = Arc::new(MockTransport::new(vec![]));
    let browser_tool = test_browser_tool(transport.clone());

    sgclaw::agent::handle_browser_message(
        transport.as_ref(),
        &browser_tool,
        BrowserMessage::Init {
            version: "1.0".to_string(),
            hmac_seed: "seed".to_string(),
            capabilities: vec![],
        },
    )
    .unwrap();
    sgclaw::agent::handle_browser_message(
        transport.as_ref(),
        &browser_tool,
        BrowserMessage::Response {
            seq: 1,
            success: true,
            data: serde_json::json!({}),
            aom_snapshot: vec![],
            timing: Timing {
                queue_ms: 1,
                exec_ms: 1,
            },
        },
    )
    .unwrap();

    assert!(transport.sent_messages().is_empty());
}
