use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{UnixListener, UnixStream};
use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};

use crate::cache::SharedCache;
use crate::registry::LookupResult;

#[derive(Debug, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum Request {
    Lookup { command: String },
    Ping,
    Shutdown,
}

#[derive(Debug, Serialize)]
pub struct Response {
    pub ok: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<LookupResult>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

impl Response {
    fn ok(result: LookupResult) -> Self {
        Self { ok: true, result: Some(result), error: None }
    }

    fn pong() -> Self {
        Self { ok: true, result: None, error: None }
    }

    fn err(msg: impl Into<String>) -> Self {
        Self { ok: false, result: None, error: Some(msg.into()) }
    }
}

/// Serve requests on a Unix domain socket.
/// `idle_timeout` triggers shutdown if no request is received within the window.
pub async fn serve(
    socket_path: &Path,
    cache: SharedCache,
    idle_timeout: Duration,
    shutdown: Arc<RwLock<bool>>,
) -> anyhow::Result<()> {
    // If a socket file exists, check whether a live daemon is already serving on it.
    // If it responds, exit immediately — we are a redundant process spawned by a race.
    if socket_path.exists() {
        if tokio::net::UnixStream::connect(socket_path).await.is_ok() {
            info!("another prvng-cli is already serving on {}; exiting", socket_path.display());
            return Ok(());
        }
        // Stale socket (process died without cleanup) — remove it before binding.
        let _ = std::fs::remove_file(socket_path);
    }

    let listener = UnixListener::bind(socket_path)?;
    info!("listening on {}", socket_path.display());

    let last_activity = Arc::new(RwLock::new(std::time::Instant::now()));

    // Idle-shutdown watchdog
    let la = last_activity.clone();
    let sd = shutdown.clone();
    tokio::spawn(async move {
        loop {
            tokio::time::sleep(Duration::from_secs(5)).await;
            let elapsed = la.read().await.elapsed();
            if elapsed >= idle_timeout {
                info!("idle timeout reached ({:.0?}), shutting down", idle_timeout);
                *sd.write().await = true;
                break;
            }
        }
    });

    loop {
        if *shutdown.read().await {
            break;
        }

        match tokio::time::timeout(Duration::from_secs(1), listener.accept()).await {
            Ok(Ok((stream, _))) => {
                *last_activity.write().await = std::time::Instant::now();
                let c = cache.clone();
                let sd = shutdown.clone();
                tokio::spawn(handle_connection(stream, c, sd));
            }
            Ok(Err(e)) => {
                error!("accept error: {}", e);
            }
            Err(_) => {} // timeout — loop to check shutdown flag
        }
    }

    if socket_path.exists() {
        let _ = std::fs::remove_file(socket_path);
    }
    info!("socket server stopped");
    Ok(())
}

async fn handle_connection(
    mut stream: UnixStream,
    cache: SharedCache,
    shutdown: Arc<RwLock<bool>>,
) {
    let mut buf = Vec::with_capacity(256);
    let mut tmp = [0u8; 512];

    loop {
        match stream.read(&mut tmp).await {
            Ok(0) => break,
            Ok(n) => buf.extend_from_slice(&tmp[..n]),
            Err(e) => {
                warn!("read error: {}", e);
                return;
            }
        }
        // Simple framing: each request is a newline-terminated JSON object.
        if buf.contains(&b'\n') {
            break;
        }
    }

    let line = buf.trim_ascii_end();
    debug!("request: {}", String::from_utf8_lossy(line));

    let response = match serde_json::from_slice::<Request>(line) {
        Ok(Request::Lookup { command }) => {
            let guard = cache.read().await;
            match &*guard {
                Some(c) => Response::ok(c.lookup(&command)),
                None => Response::err("cache not loaded"),
            }
        }
        Ok(Request::Ping) => Response::pong(),
        Ok(Request::Shutdown) => {
            *shutdown.write().await = true;
            Response::pong()
        }
        Err(e) => Response::err(format!("bad request: {}", e)),
    };

    let mut out = serde_json::to_vec(&response).unwrap_or_default();
    out.push(b'\n');
    let _ = stream.write_all(&out).await;
}
