use std::time::Duration;

use anyhow::Result;

/// HTTP Connectivity module for checking HTTP/HTTPS connections.
pub struct HttpConnector {
    timeout: Duration,
}

impl HttpConnector {
    /// Creates a new HTTP connector with the default configuration.
    pub fn new() -> Self {
        Self {
            timeout: Duration::from_secs(30),
        }
    }

    /// Creates a new HTTP connector with a custom timeout.
    pub fn with_timeout(timeout: Duration) -> Self {
        Self { timeout }
    }

    /// Checks if a URL is reachable.
    pub fn check_url(&self, url: &str) -> Result<bool> {
        let agent = ureq::Agent::new_with_config(
            ureq::Agent::config_builder()
                .timeout_global(Some(self.timeout))
                .build(),
        );

        match agent.head(url).call() {
            Ok(_) => Ok(true),
            Err(_) => Ok(false),
        }
    }

    /// Checks a URL and returns the status code if reachable.
    pub fn check_status(&self, url: &str) -> Result<Option<u16>> {
        let agent = ureq::Agent::new_with_config(
            ureq::Agent::config_builder()
                .timeout_global(Some(self.timeout))
                .build(),
        );

        match agent.head(url).call() {
            Ok(response) => Ok(Some(response.status().as_u16())),
            Err(_) => Ok(None),
        }
    }

    /// Gets the content of a URL.
    pub fn get_content(&self, url: &str) -> Result<String> {
        let agent = ureq::Agent::new_with_config(
            ureq::Agent::config_builder()
                .timeout_global(Some(self.timeout))
                .build(),
        );

        let mut response = agent.get(url).call()?;

        if !response.status().is_success() {
            return Err(anyhow::anyhow!(
                "HTTP request failed with status: {}",
                response.status()
            ));
        }

        let content = response.body_mut().read_to_string()?;
        Ok(content)
    }

    /// Verifies that a URL responds with a specific status code.
    pub fn verify_status(&self, url: &str, expected_status: u16) -> Result<bool> {
        match self.check_status(url)? {
            Some(status) => Ok(status == expected_status),
            None => Ok(false),
        }
    }
}

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

/// Send a single HTTP GET request to check if an endpoint is available
///
/// Performs a single HTTP GET request to the specified URL and returns true if
/// the response status is 2xx (success).
///
/// # Arguments
///
/// * `url` - URL to ping (e.g., "http://localhost:8080/health")
///
/// # Returns
///
/// * `Ok(true)` - HTTP request succeeded with 2xx status
/// * `Ok(false)` - HTTP request failed or returned non-2xx status
/// * `Err` - If request could not be completed
///
/// # Example
///
/// ```no_run
/// use herolib_os::net::http::http_ping;
///
/// if http_ping("http://localhost:8080/health")? {
///     println!("Service is healthy");
/// } else {
///     println!("Service health check failed");
/// }
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn http_ping(url: &str) -> Result<bool> {
    use std::process::Command;

    // Use curl to perform a simple HTTP request
    let output = Command::new("curl")
        .arg("-s")
        .arg("-o")
        .arg("/dev/null")
        .arg("-w")
        .arg("%{http_code}")
        .arg(url)
        .output()
        .map_err(|e| anyhow::anyhow!("curl failed: {}", e))?;

    if output.status.success() {
        let status_code = String::from_utf8_lossy(&output.stdout);
        // Check if status code is 2xx
        let code: i32 = status_code
            .trim()
            .parse()
            .map_err(|_| anyhow::anyhow!("Failed to parse HTTP status code"))?;

        Ok(code >= 200 && code < 300)
    } else {
        Err(anyhow::anyhow!("curl command failed"))
    }
}

/// Wait for an HTTP endpoint to become available
///
/// Repeatedly attempts to connect to the specified URL until it succeeds or the timeout
/// is reached. Checks every 500ms.
///
/// # Arguments
///
/// * `url` - URL to wait for (e.g., "http://localhost:8080/health")
/// * `timeout_seconds` - Maximum time to wait in seconds
///
/// # Returns
///
/// * `Ok(())` - Endpoint became available
/// * `Err` - Timeout or other error
///
/// # Example
///
/// ```no_run
/// use herolib_os::net::http::http_wait;
///
/// http_wait("http://localhost:8080/health", 30)?;
/// println!("Service is ready");
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn http_wait(url: &str, timeout_seconds: i64) -> Result<()> {
    use std::thread;
    use std::time::Duration;

    if timeout_seconds < 0 {
        return Err(anyhow::anyhow!("Timeout must be non-negative"));
    }

    let timeout = Duration::from_secs(timeout_seconds as u64);
    let start = std::time::Instant::now();
    let check_interval = Duration::from_millis(500);

    loop {
        // Check if endpoint is available
        match http_ping(url) {
            Ok(true) => return Ok(()), // Endpoint is available
            Ok(false) => {}            // Endpoint not ready yet
            Err(_) => {}               // Connection failed, try again
        }

        // Check if timeout exceeded
        if start.elapsed() > timeout {
            return Err(anyhow::anyhow!(
                "Timeout waiting for {} to become available after {} seconds",
                url,
                timeout_seconds
            ));
        }

        // Wait before retrying
        thread::sleep(check_interval);
    }
}
