use std::collections::HashMap;

use crate::error::{ConfigError, Result};

/// Client for interacting with OCI registries (Zot, Docker Registry, etc.)
#[derive(Debug, Clone)]
pub struct OciClient {
    registry_url: String,
    #[allow(dead_code)]
    username: Option<String>,
    #[allow(dead_code)]
    password: Option<String>,
    tls_verify: bool,
}

/// OCI Image metadata
#[derive(Debug, Clone)]
pub struct ImageMetadata {
    pub name: String,
    pub tag: String,
    pub digest: String,
    pub size_bytes: u64,
    pub created: String,
    pub config: Option<HashMap<String, String>>,
}

/// OCI Repository information
#[derive(Debug, Clone)]
pub struct RepositoryInfo {
    pub name: String,
    pub tag_count: usize,
    pub manifest_count: usize,
}

impl OciClient {
    /// Create a new OCI client
    pub fn new(registry_url: String, tls_verify: bool) -> Self {
        Self {
            registry_url,
            username: None,
            password: None,
            tls_verify,
        }
    }

    /// Create a new OCI client with authentication
    pub fn with_auth(
        registry_url: String,
        username: String,
        password: String,
        tls_verify: bool,
    ) -> Self {
        Self {
            registry_url,
            username: Some(username),
            password: Some(password),
            tls_verify,
        }
    }

    /// Get the registry URL
    pub fn registry_url(&self) -> &str {
        &self.registry_url
    }

    /// Check if registry is accessible
    pub async fn health_check(&self) -> Result<bool> {
        let url = format!("{}/v2/_catalog", self.registry_url);

        match reqwest::Client::builder()
            .danger_accept_invalid_certs(!self.tls_verify)
            .build()
        {
            Ok(client) => match client.get(&url).send().await {
                Ok(response) => Ok(response.status().is_success()),
                Err(e) => {
                    tracing::warn!("OCI registry health check failed: {}", e);
                    Err(ConfigError::io_error(format!(
                        "Failed to reach OCI registry at {}: {}",
                        self.registry_url, e
                    )))
                }
            },
            Err(e) => Err(ConfigError::io_error(format!(
                "Failed to create HTTP client: {}",
                e
            ))),
        }
    }

    /// List all repositories in the registry
    pub async fn list_repositories(&self) -> Result<Vec<String>> {
        let url = format!("{}/v2/_catalog", self.registry_url);

        match reqwest::Client::builder()
            .danger_accept_invalid_certs(!self.tls_verify)
            .build()
        {
            Ok(client) => match client.get(&url).send().await {
                Ok(response) => match response.json::<serde_json::Value>().await {
                    Ok(data) => {
                        if let Some(repos) = data.get("repositories").and_then(|v| v.as_array()) {
                            let repository_names: Vec<String> = repos
                                .iter()
                                .filter_map(|v| v.as_str().map(|s| s.to_string()))
                                .collect();
                            Ok(repository_names)
                        } else {
                            Ok(Vec::new())
                        }
                    }
                    Err(e) => {
                        tracing::warn!("Failed to parse registry response: {}", e);
                        Err(ConfigError::deserialization_failed(format!(
                            "Invalid registry response: {}",
                            e
                        )))
                    }
                },
                Err(e) => Err(ConfigError::io_error(format!(
                    "Failed to list repositories: {}",
                    e
                ))),
            },
            Err(e) => Err(ConfigError::io_error(format!(
                "Failed to create HTTP client: {}",
                e
            ))),
        }
    }

    /// List all tags for a repository
    pub async fn list_tags(&self, repository: &str) -> Result<Vec<String>> {
        let url = format!(
            "{}/v2/{}/tags/list",
            self.registry_url,
            urlencoding::encode(repository)
        );

        match reqwest::Client::builder()
            .danger_accept_invalid_certs(!self.tls_verify)
            .build()
        {
            Ok(client) => match client.get(&url).send().await {
                Ok(response) => match response.json::<serde_json::Value>().await {
                    Ok(data) => {
                        if let Some(tags) = data.get("tags").and_then(|v| v.as_array()) {
                            let tag_names: Vec<String> = tags
                                .iter()
                                .filter_map(|v| v.as_str().map(|s| s.to_string()))
                                .collect();
                            Ok(tag_names)
                        } else {
                            Ok(Vec::new())
                        }
                    }
                    Err(e) => {
                        tracing::warn!("Failed to parse tags response: {}", e);
                        Err(ConfigError::deserialization_failed(format!(
                            "Invalid tags response: {}",
                            e
                        )))
                    }
                },
                Err(e) => Err(ConfigError::io_error(format!(
                    "Failed to list tags for {}: {}",
                    repository, e
                ))),
            },
            Err(e) => Err(ConfigError::io_error(format!(
                "Failed to create HTTP client: {}",
                e
            ))),
        }
    }

    /// Get manifest for an image
    pub async fn get_manifest(&self, repository: &str, tag: &str) -> Result<serde_json::Value> {
        let url = format!(
            "{}/v2/{}/manifests/{}",
            self.registry_url,
            urlencoding::encode(repository),
            urlencoding::encode(tag)
        );

        match reqwest::Client::builder()
            .danger_accept_invalid_certs(!self.tls_verify)
            .build()
        {
            Ok(client) => match client.get(&url).send().await {
                Ok(response) => match response.json::<serde_json::Value>().await {
                    Ok(manifest) => Ok(manifest),
                    Err(e) => Err(ConfigError::deserialization_failed(format!(
                        "Failed to parse manifest: {}",
                        e
                    ))),
                },
                Err(e) => Err(ConfigError::io_error(format!(
                    "Failed to get manifest for {}:{}: {}",
                    repository, tag, e
                ))),
            },
            Err(e) => Err(ConfigError::io_error(format!(
                "Failed to create HTTP client: {}",
                e
            ))),
        }
    }
}

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

    #[test]
    fn test_oci_client_new() {
        let client = OciClient::new("http://localhost:5001".to_string(), true);
        assert_eq!(client.registry_url(), "http://localhost:5001");
        assert!(client.tls_verify);
        assert!(client.username.is_none());
    }

    #[test]
    fn test_oci_client_with_auth() {
        let client = OciClient::with_auth(
            "http://localhost:5001".to_string(),
            "user".to_string(),
            "pass".to_string(),
            false,
        );
        assert_eq!(client.registry_url(), "http://localhost:5001");
        assert!(!client.tls_verify);
        assert!(client.username.is_some());
        assert!(client.password.is_some());
    }

    #[tokio::test]
    async fn test_oci_health_check_localhost() {
        // This test requires Zot running at localhost:5001
        let client = OciClient::new("http://localhost:5001".to_string(), false);
        match client.health_check().await {
            Ok(healthy) => {
                println!(
                    "✓ Zot registry health: {}",
                    if healthy { "OK" } else { "UNHEALTHY" }
                );
            }
            Err(e) => {
                eprintln!("ℹ Zot registry unavailable (expected in CI): {}", e);
            }
        }
    }

    #[tokio::test]
    async fn test_oci_list_repositories_empty() {
        // This test requires Zot running at localhost:5001
        let client = OciClient::new("http://localhost:5001".to_string(), false);
        match client.list_repositories().await {
            Ok(repos) => {
                println!("✓ Found {} repositories in Zot", repos.len());
            }
            Err(e) => {
                eprintln!("ℹ Could not list repositories (expected in CI): {}", e);
            }
        }
    }
}
