use std::{
    ffi::CString,
    sync::{LazyLock, Mutex, mpsc},
    thread::{self, JoinHandle},
};

use axum::Router;
use rmcp::transport::streamable_http_server::{StreamableHttpServerConfig, StreamableHttpService};
use tokio::net::TcpListener;
use tokio_util::sync::CancellationToken;
use windows::{
    Win32::System::Diagnostics::Debug::Extensions::{
        DEBUG_CONNECT_SESSION_NO_ANNOUNCE, DEBUG_CONNECT_SESSION_NO_VERSION, DEBUG_OUTPUT_NORMAL,
        IDebugControl,
    },
    core::{Interface, PCSTR},
};

use crate::{
    CommandDispatcher, ExecutionMode, WindbgMcpServer, primary_client::create_client_from_primary,
};

const DEFAULT_BIND_ADDRESS: &str = "127.0.0.1:50051";

static SERVER_STATE: LazyLock<Mutex<Option<RunningPluginServer>>> =
    LazyLock::new(|| Mutex::new(None));
static DISPATCHER_STATE: LazyLock<Mutex<Option<CommandDispatcher>>> =
    LazyLock::new(|| Mutex::new(None));

#[derive(Debug, Clone)]
pub struct PluginServerStatus {
    pub mcp_url: String,
}

struct RunningPluginServer {
    status: PluginServerStatus,
    cancellation: CancellationToken,
    join_handle: JoinHandle<()>,
}

pub struct PluginServerControl;

impl PluginServerControl {
    pub fn get_or_start_dispatcher() -> Result<CommandDispatcher, String> {
        let existing = {
            let state = DISPATCHER_STATE
                .lock()
                .map_err(|_| "dispatcher state lock poisoned".to_string())?;
            state.clone()
        };

        if let Some(dispatcher) = existing {
            return Ok(dispatcher);
        }

        match CommandDispatcher::spawn(ExecutionMode::CurrentSession) {
            Ok(dispatcher) => {
                let mut state = DISPATCHER_STATE
                    .lock()
                    .map_err(|_| "dispatcher state lock poisoned".to_string())?;
                if let Some(existing) = state.as_ref() {
                    return Ok(existing.clone());
                }
                let cloned = dispatcher.clone();
                *state = Some(dispatcher);
                let _ = notify_windbg("WinDbg MCP debugger session connected.\n");
                Ok(cloned)
            }
            Err(error) => {
                let message = error.to_string();
                let _ = notify_windbg(&format!(
                    "WinDbg MCP debugger session connection failed: {message}\n"
                ));
                Err(message)
            }
        }
    }

    pub fn start(bind_address: Option<&str>) -> Result<PluginServerStatus, String> {
        let bind_address = bind_address
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .unwrap_or(DEFAULT_BIND_ADDRESS)
            .to_string();

        let mut state = SERVER_STATE
            .lock()
            .map_err(|_| "server state lock poisoned".to_string())?;
        if let Some(existing) = state.as_ref() {
            return Ok(existing.status.clone());
        }

        let cancellation = CancellationToken::new();
        let cancellation_for_thread = cancellation.clone();
        let (startup_tx, startup_rx) = mpsc::channel::<Result<PluginServerStatus, String>>();
        let thread_bind = bind_address.clone();

        let join_handle = thread::Builder::new()
            .name("windbg-mcp-plugin-server".to_string())
            .spawn(move || {
                let startup_error_tx = startup_tx.clone();
                let runtime = match tokio::runtime::Builder::new_multi_thread()
                    .enable_io()
                    .enable_time()
                    .build()
                {
                    Ok(runtime) => runtime,
                    Err(error) => {
                        let _ = startup_tx.send(Err(error.to_string()));
                        return;
                    }
                };

                let result = runtime.block_on(async move {
                    run_server_loop(thread_bind, cancellation_for_thread, startup_tx).await
                });

                if let Err(error) = result {
                    let _ = startup_error_tx.send(Err(error.clone()));
                    tracing::error!("plugin MCP server stopped with error: {error}");
                }
            })
            .map_err(|error| error.to_string())?;

        let status = startup_rx
            .recv()
            .map_err(|_| "plugin server failed to report startup status".to_string())??;

        *state = Some(RunningPluginServer {
            status: status.clone(),
            cancellation,
            join_handle,
        });

        Ok(status)
    }

    pub fn status() -> Result<Option<PluginServerStatus>, String> {
        let state = SERVER_STATE
            .lock()
            .map_err(|_| "server state lock poisoned".to_string())?;
        Ok(state.as_ref().map(|running| running.status.clone()))
    }

    pub fn stop() -> Result<Option<PluginServerStatus>, String> {
        let running = {
            let mut state = SERVER_STATE
                .lock()
                .map_err(|_| "server state lock poisoned".to_string())?;
            state.take()
        };

        if let Ok(mut dispatcher_state) = DISPATCHER_STATE.lock() {
            dispatcher_state.take();
        }

        let Some(running) = running else {
            return Ok(None);
        };

        running.cancellation.cancel();
        running
            .join_handle
            .join()
            .map_err(|_| "plugin server thread panicked".to_string())?;
        Ok(Some(running.status))
    }
}

async fn run_server_loop(
    bind_address: String,
    cancellation: CancellationToken,
    startup_tx: mpsc::Sender<Result<PluginServerStatus, String>>,
) -> Result<(), String> {
    let listener = TcpListener::bind(&bind_address)
        .await
        .map_err(|error| error.to_string())?;
    let local_addr = listener.local_addr().map_err(|error| error.to_string())?;
    let status = PluginServerStatus {
        mcp_url: format!("http://{}:{}/mcp", local_addr.ip(), local_addr.port()),
    };
    startup_tx
        .send(Ok(status))
        .map_err(|_| "plugin server startup receiver dropped".to_string())?;

    let service: StreamableHttpService<WindbgMcpServer> = StreamableHttpService::new(
        || Ok(WindbgMcpServer::new()),
        Default::default(),
        StreamableHttpServerConfig {
            stateful_mode: true,
            sse_keep_alive: None,
            cancellation_token: cancellation.child_token(),
            ..Default::default()
        },
    );
    let router = Router::new().nest_service("/mcp", service);

    axum::serve(listener, router)
        .with_graceful_shutdown(async move { cancellation.cancelled_owned().await })
        .await
        .map_err(|error| error.to_string())
}

pub(crate) fn notify_windbg(text: &str) -> Result<(), String> {
    let client = create_client_from_primary()?;
    unsafe {
        client
            .ConnectSession(
                DEBUG_CONNECT_SESSION_NO_VERSION | DEBUG_CONNECT_SESSION_NO_ANNOUNCE,
                0,
            )
            .map_err(|error| error.to_string())?;
    }

    let control = client
        .cast::<IDebugControl>()
        .map_err(|error| error.to_string())?;

    for line in text.lines() {
        let mut escaped = line.replace('%', "%%");
        escaped.push('\n');
        let c_text = CString::new(escaped).map_err(|_| "output text contained NUL".to_string())?;
        unsafe {
            control
                .Output(DEBUG_OUTPUT_NORMAL, PCSTR(c_text.as_ptr() as _))
                .map_err(|error| error.to_string())?;
        }
    }

    Ok(())
}
