mod protocol;
pub(crate) mod server;

use std::net::TcpListener;
use std::sync::Arc;

use tungstenite::accept;

use crate::agent::AgentRuntimeContext;
use crate::pipe::PipeError;
use crate::security::MacPolicy;

const DEFAULT_BROWSER_WS_URL: &str = "ws://127.0.0.1:12345";
const DEFAULT_SERVICE_WS_LISTEN_ADDR: &str = "127.0.0.1:42321";

pub use protocol::{ClientMessage, ServiceMessage};
pub use server::{serve_client, ServiceEventSink, ServiceSession};

pub(crate) mod browser_ws_client {
    pub(crate) use super::server::{initial_request_url_for_submit_task, ServiceWsClient};
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ServiceStartupConfig {
    pub browser_ws_url: Option<String>,
    pub service_ws_listen_addr: Option<String>,
}

pub fn load_startup_config(
    runtime_context: &AgentRuntimeContext,
) -> Result<ServiceStartupConfig, PipeError> {
    let settings = runtime_context
        .load_sgclaw_settings()?
        .ok_or_else(|| PipeError::Protocol("missing environment variable: DEEPSEEK_API_KEY".to_string()))?;

    Ok(ServiceStartupConfig {
        browser_ws_url: Some(
            settings
                .browser_ws_url
                .unwrap_or_else(|| DEFAULT_BROWSER_WS_URL.to_string()),
        ),
        service_ws_listen_addr: Some(
            settings
                .service_ws_listen_addr
                .unwrap_or_else(|| DEFAULT_SERVICE_WS_LISTEN_ADDR.to_string()),
        ),
    })
}

pub fn run() -> Result<(), PipeError> {
    let runtime_context = AgentRuntimeContext::from_process_args(std::env::args_os())?;
    let startup = load_startup_config(&runtime_context)?;
    let service_ws_listen_addr = startup
        .service_ws_listen_addr
        .as_deref()
        .unwrap_or(DEFAULT_SERVICE_WS_LISTEN_ADDR);
    let browser_ws_url = startup
        .browser_ws_url
        .as_deref()
        .unwrap_or(DEFAULT_BROWSER_WS_URL);
    let listener = TcpListener::bind(service_ws_listen_addr)
        .map_err(|err| PipeError::Protocol(format!("failed to bind service listener {service_ws_listen_addr}: {err}")))?;
    let mac_policy = load_service_mac_policy()?;
    let session = ServiceSession::new();

    eprintln!(
        "sg_claw ready: service_ws_listen_addr={}, browser_ws_url={}",
        service_ws_listen_addr,
        browser_ws_url,
    );

    loop {
        let (stream, _) = listener.accept()?;
        let websocket = accept(stream)
            .map_err(|err| PipeError::Protocol(format!("service websocket accept failed: {err}")))?;
        let sink = Arc::new(ServiceEventSink::from_websocket(websocket));
        match session.try_attach_client() {
            Ok(()) => {
                let result = serve_client(
                    &runtime_context,
                    &session,
                    sink.clone(),
                    browser_ws_url,
                    &mac_policy,
                );
                session.detach_client();
                match result {
                    Ok(()) | Err(PipeError::PipeClosed) => {}
                    Err(err) => return Err(err),
                }
            }
            Err(message) => {
                sink.send_service_message(message)?;
            }
        }
    }
}

fn load_service_mac_policy() -> Result<MacPolicy, PipeError> {
    let current_exe = std::env::current_exe()?;
    let candidate = current_exe
        .parent()
        .map(|dir| dir.join("resources").join("rules.json"))
        .unwrap_or_else(|| std::path::PathBuf::from("resources").join("rules.json"));
    let path = if candidate.exists() {
        candidate
    } else {
        std::env::current_dir()?.join("resources").join("rules.json")
    };
    MacPolicy::load_from_path(&path).map_err(PipeError::from)
}
