//! Health check system
//!
//! Monitors ship health via HTTP endpoints or PID status.

use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::UnixStream;
use tracing::{debug, warn};

/// Health check interval (opinionated: 5 seconds)
const HEALTH_CHECK_INTERVAL: Duration = Duration::from_secs(5);

/// Health check timeout
const HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(2);

/// Health checker for ships
pub struct HealthChecker {
    client: reqwest::Client,
}

impl Default for HealthChecker {
    fn default() -> Self {
        Self::new()
    }
}

impl HealthChecker {
    /// Create a new health checker
    pub fn new() -> Self {
        let client = reqwest::Client::builder()
            .timeout(HEALTH_CHECK_TIMEOUT)
            .build()
            .expect("Failed to build HTTP client");

        Self { client }
    }

    /// Check health of a URL endpoint (HTTP or Unix socket)
    pub async fn check_http(&self, url: &str) -> bool {
        // Handle Unix socket URLs: unix:///path/to/socket/endpoint
        if let Some(rest) = url.strip_prefix("unix://") {
            return self.check_unix_socket(rest).await;
        }

        // Standard HTTP check
        match self.client.get(url).send().await {
            Ok(resp) => {
                let healthy = resp.status().is_success();
                debug!(url = url, status = %resp.status(), healthy = healthy, "Health check");
                healthy
            }
            Err(e) => {
                warn!(url = url, error = %e, "Health check failed");
                false
            }
        }
    }

    /// Check health via Unix socket
    /// Format: /path/to/socket/endpoint (e.g., /tmp/app.sock/health)
    async fn check_unix_socket(&self, path_and_endpoint: &str) -> bool {
        // Find where the socket path ends and endpoint begins
        // Strategy: try progressively shorter paths until we find a socket file
        let (socket_path, endpoint) = match Self::split_socket_path(path_and_endpoint) {
            Some(parts) => parts,
            None => {
                warn!(path = path_and_endpoint, "Could not find Unix socket file");
                return false;
            }
        };

        let result = tokio::time::timeout(HEALTH_CHECK_TIMEOUT, async {
            let mut stream = UnixStream::connect(&socket_path).await?;

            // Send minimal HTTP/1.1 request
            let request = format!(
                "GET {} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n",
                endpoint
            );
            stream.write_all(request.as_bytes()).await?;

            // Read response
            let mut response = vec![0u8; 1024];
            let n = stream.read(&mut response).await?;
            let response_str = String::from_utf8_lossy(&response[..n]);

            // Check for 2xx status
            Ok::<bool, std::io::Error>(
                response_str.starts_with("HTTP/1.1 2") || response_str.starts_with("HTTP/1.0 2"),
            )
        })
        .await;

        match result {
            Ok(Ok(healthy)) => {
                debug!(socket = socket_path, endpoint = endpoint, healthy = healthy, "Unix socket health check");
                healthy
            }
            Ok(Err(e)) => {
                warn!(socket = socket_path, error = %e, "Unix socket health check failed");
                false
            }
            Err(_) => {
                warn!(socket = socket_path, "Unix socket health check timed out");
                false
            }
        }
    }

    /// Split a path like "/tmp/app.sock/health" into ("/tmp/app.sock", "/health")
    fn split_socket_path(path: &str) -> Option<(String, String)> {
        // Common socket extensions
        for ext in [".sock", ".socket", ".s"] {
            if let Some(idx) = path.find(ext) {
                let socket_end = idx + ext.len();
                let socket_path = &path[..socket_end];
                let endpoint = if socket_end < path.len() {
                    &path[socket_end..]
                } else {
                    "/"
                };
                return Some((socket_path.to_string(), endpoint.to_string()));
            }
        }

        // Fallback: check if file exists at progressively shorter paths
        let parts: Vec<&str> = path.split('/').collect();
        for i in (1..parts.len()).rev() {
            let potential_socket = parts[..=i].join("/");
            if std::path::Path::new(&potential_socket).exists() {
                let endpoint = if i + 1 < parts.len() {
                    format!("/{}", parts[i + 1..].join("/"))
                } else {
                    "/".to_string()
                };
                return Some((potential_socket, endpoint));
            }
        }

        None
    }

    /// Get the check interval
    pub fn interval(&self) -> Duration {
        HEALTH_CHECK_INTERVAL
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_health_checker() {
        let checker = HealthChecker::new();
        // This should fail (no server running)
        let healthy = checker.check_http("http://127.0.0.1:19999/health").await;
        assert!(!healthy);
    }
}
