use std::net::TcpStream;
use std::process::{Command, Stdio};
use std::time::Duration;

use anyhow::{Context, Result};

use fyi_core::{Config, ExportLogsServiceRequest, ParsedApiRequest, ParsedCodexEvent, Storage};

const DEFAULT_PORT: u16 = 4318;
const HEALTH_RESPONSE: &str = r#"{"service":"fyi-otel","status":"ok"}"#;

/// Check if the OTel server is running on the given port
pub fn is_server_running(port: u16) -> bool {
    TcpStream::connect_timeout(
        &format!("127.0.0.1:{}", port)
            .parse()
            .ok()
            .unwrap_or_else(|| std::net::SocketAddr::from(([127, 0, 0, 1], port))),
        Duration::from_millis(100),
    )
    .is_ok()
}

/// Check if the server on this port is a fyi OTel server
/// Returns true if it's our server, false if it's something else
pub fn is_fyi_server(port: u16) -> bool {
    use std::io::{Read, Write};

    let addr = format!("127.0.0.1:{}", port);
    let Ok(mut stream) = TcpStream::connect_timeout(
        &addr
            .parse()
            .ok()
            .unwrap_or_else(|| std::net::SocketAddr::from(([127, 0, 0, 1], port))),
        Duration::from_millis(500),
    ) else {
        return false;
    };

    // Set read timeout
    let _ = stream.set_read_timeout(Some(Duration::from_millis(500)));

    // Send HTTP GET /health request
    let request = "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
    if stream.write_all(request.as_bytes()).is_err() {
        return false;
    }

    // Read response - check for our specific service identifier
    let mut response = [0u8; 256];
    if stream.read(&mut response).is_err() {
        return false;
    }

    // Check for our specific response containing "fyi-otel"
    let response_str = String::from_utf8_lossy(&response);
    response_str.contains("fyi-otel")
}

/// Kill the fyi OTel server running on the given port
#[cfg(unix)]
fn kill_fyi_server(port: u16) -> Result<bool> {
    // Use lsof to find the PID of the process listening on this port
    let output = Command::new("lsof")
        .args(["-ti", &format!(":{}", port)])
        .output()
        .context("failed to run lsof")?;

    if output.status.success() {
        let pids = String::from_utf8_lossy(&output.stdout);
        let mut killed = false;
        for pid in pids.lines() {
            let pid = pid.trim();
            if !pid.is_empty() {
                // Use kill command to send SIGTERM
                let _ = Command::new("kill").args(["-TERM", pid]).output();
                killed = true;
            }
        }
        if killed {
            // Wait a moment for the process to terminate
            std::thread::sleep(Duration::from_millis(500));
            return Ok(true);
        }
    }
    Ok(false)
}

#[cfg(not(unix))]
fn kill_fyi_server(_port: u16) -> Result<bool> {
    anyhow::bail!("stop is not supported on this platform")
}

/// Stop the fyi OTel server running on the given port
pub fn stop_server(port: u16) -> Result<()> {
    if !is_server_running(port) {
        eprintln!("No server running on port {}", port);
        return Ok(());
    }

    if !is_fyi_server(port) {
        anyhow::bail!(
            "port {} is in use by another service, not a fyi OTel server",
            port
        );
    }

    if kill_fyi_server(port)? {
        eprintln!("Stopped OTel server on port {}", port);
    }
    Ok(())
}

/// Get the status of the OTel server
pub fn get_status(port: u16) -> Result<()> {
    if !is_server_running(port) {
        eprintln!("OTel server is not running on port {}", port);
        return Ok(());
    }

    if is_fyi_server(port) {
        eprintln!("OTel server is running on port {}", port);
    } else {
        eprintln!(
            "Port {} is in use by another service (not a fyi OTel server)",
            port
        );
    }
    Ok(())
}

/// Ensure the OTel server is running, starting it if necessary.
/// Returns `true` if the server is confirmed running, `false` if it may still be starting.
pub fn ensure_running(port: u16, restart: bool) -> Result<bool> {
    if is_server_running(port) {
        // Something is running on this port - check if it's our server
        if is_fyi_server(port) {
            if restart {
                // Kill and restart
                kill_fyi_server(port)?;
            } else {
                return Ok(true); // Our server is already running
            }
        } else {
            // Port is in use by something else
            anyhow::bail!(
                "port {} is in use by another service; \
                 cannot start fyi OTel server; \
                 consider changing the port in your settings",
                port
            );
        }
    }

    // Find the fyi binary path
    let fyi_path = std::env::current_exe().context("failed to determine fyi binary path")?;

    // Spawn the server in background
    let mut cmd = Command::new(&fyi_path);
    cmd.args(["otel", "run", "--port", &port.to_string()])
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null());

    // Platform-specific daemonization using nohup to survive parent exit
    #[cfg(unix)]
    {
        // Use nohup to make process immune to SIGHUP and parent death
        let mut nohup_cmd = Command::new("nohup");
        nohup_cmd
            .arg(&fyi_path)
            .args(["otel", "run", "--port", &port.to_string()])
            .stdin(Stdio::null())
            .stdout(Stdio::null())
            .stderr(Stdio::null());

        use std::os::unix::process::CommandExt;
        nohup_cmd.process_group(0);

        nohup_cmd.spawn().context("failed to spawn otel server")?;
    }

    #[cfg(not(unix))]
    cmd.spawn().context("failed to spawn otel server")?;

    // Wait briefly for server to start
    for _ in 0..10 {
        std::thread::sleep(Duration::from_millis(50));
        if is_server_running(port) {
            return Ok(true);
        }
    }

    // Server may still be starting - warn user but don't fail
    eprintln!(
        "Warning: OTel server not yet responding on port {}; it may still be starting",
        port
    );
    Ok(false)
}

/// Run the OTLP HTTP server
pub fn run_server<S: Storage>(storage: &S, port: u16) -> Result<()> {
    let config = Config::load().unwrap_or_default();
    let machine_id = config.machine_id();

    let addr = format!("127.0.0.1:{}", port);
    let server = tiny_http::Server::http(&addr)
        .map_err(|e| anyhow::anyhow!("failed to start HTTP server: {}", e))?;

    eprintln!("[fyi] OTel server listening on http://{}", addr);

    loop {
        let mut request = match server.recv() {
            Ok(req) => req,
            Err(e) => {
                eprintln!("[fyi] Error receiving request: {}", e);
                continue;
            }
        };

        let url = request.url().to_string();
        let method = request.method().to_string();

        // Handle POST /v1/logs
        if method == "POST" && url == "/v1/logs" {
            let mut body = String::new();
            if request.as_reader().read_to_string(&mut body).is_ok()
                && let Ok(logs_request) = serde_json::from_str::<ExportLogsServiceRequest>(&body)
            {
                let count = process_logs_request(storage, &logs_request, &machine_id);
                if count > 0 {
                    eprintln!("[fyi] Stored {} API request(s)", count);
                }
            }
            let response = tiny_http::Response::empty(200);
            let _ = request.respond(response);
        } else if url == "/health" {
            // Return specific JSON response for service identification
            let response = tiny_http::Response::from_string(HEALTH_RESPONSE).with_header(
                tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"application/json"[..])
                    .ok()
                    .unwrap_or_else(|| {
                        // Fallback - this should never fail with valid header bytes
                        tiny_http::Header::from_bytes(
                            &b"Content-Type"[..],
                            &b"application/json"[..],
                        )
                        .ok()
                        .unwrap_or_else(|| unreachable!())
                    }),
            );
            let _ = request.respond(response);
        } else {
            // Unknown endpoint
            let response = tiny_http::Response::empty(404);
            let _ = request.respond(response);
        }
    }
}

fn process_logs_request<S: Storage>(
    storage: &S,
    request: &ExportLogsServiceRequest,
    machine_id: &str,
) -> usize {
    let mut count = 0;

    for resource_logs in &request.resource_logs {
        for scope_logs in &resource_logs.scope_logs {
            for log_record in &scope_logs.log_records {
                // Try to parse as API request (token-bearing event)
                if let Some(parsed) = ParsedApiRequest::from_log_record(log_record)
                    && let Some(event) = parsed.into_event(machine_id.to_string())
                    && storage.insert(&event).is_ok()
                {
                    count += 1;
                    continue;
                }

                // Try to parse as Codex activity event (non-token event)
                if let Some(codex_event) = ParsedCodexEvent::from_log_record(log_record) {
                    let event = codex_event.into_event(machine_id.to_string());
                    if storage.insert(&event).is_ok() {
                        count += 1;
                    }
                }
            }
        }
    }

    count
}

/// Get the default OTel port
pub fn default_port() -> u16 {
    DEFAULT_PORT
}
