//! OCI Registry Client Implementation
//!
//! Client for OCI Distribution Spec v2 compliant registries.

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use tracing::{debug, error};

/// OCI artifact metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OciArtifact {
    pub name: String,
    pub version: String,
    pub digest: String,
    pub size: u64,
    pub media_type: String,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

/// OCI manifest
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OciManifest {
    pub schema_version: u32,
    pub media_type: String,
    pub config: OciDescriptor,
    pub layers: Vec<OciDescriptor>,
    pub annotations: Option<std::collections::HashMap<String, String>>,
}

/// OCI descriptor
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OciDescriptor {
    pub media_type: String,
    pub digest: String,
    pub size: u64,
    pub annotations: Option<std::collections::HashMap<String, String>>,
}

/// Downloaded artifact with content
#[derive(Debug, Clone)]
pub struct DownloadedArtifact {
    pub metadata: OciArtifact,
    pub content: Vec<u8>,
}

/// OCI registry client
pub struct OciClient {
    registry_url: String,
    namespace: String,
    client: reqwest::Client,
}

impl OciClient {
    /// Create new OCI client
    pub fn new(registry_url: String, namespace: String) -> Self {
        Self {
            registry_url,
            namespace,
            client: reqwest::Client::new(),
        }
    }

    /// Pull artifact from registry
    pub async fn pull_artifact(&self, name: &str, version: &str) -> Result<DownloadedArtifact> {
        debug!("Pulling artifact: {}:{}", name, version);

        // Get manifest first
        let manifest = self.get_manifest(name, version).await?;

        // Download layers
        let mut content = Vec::new();

        for layer in &manifest.layers {
            let layer_content = self.download_blob(name, &layer.digest).await?;
            content.extend_from_slice(&layer_content);
        }

        let metadata = OciArtifact {
            name: name.to_string(),
            version: version.to_string(),
            digest: manifest.config.digest.clone(),
            size: content.len() as u64,
            media_type: manifest.media_type.clone(),
            created_at: chrono::Utc::now(),
        };

        debug!("Successfully pulled artifact: {}:{}", name, version);

        Ok(DownloadedArtifact { metadata, content })
    }

    /// Get manifest from registry
    pub async fn get_manifest(&self, name: &str, version: &str) -> Result<OciManifest> {
        debug!("Getting manifest for {}:{}", name, version);

        let url = format!(
            "{}/v2/{}/{}/manifests/{}",
            self.registry_url, self.namespace, name, version
        );

        let response = self
            .client
            .get(&url)
            .header("Accept", "application/vnd.oci.image.manifest.v1+json")
            .send()
            .await
            .context("Failed to send manifest request")?;

        if response.status().is_success() {
            let manifest: OciManifest =
                response.json().await.context("Failed to parse manifest")?;

            debug!("Retrieved manifest for {}:{}", name, version);
            Ok(manifest)
        } else {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());

            error!(
                "Failed to get manifest for {}:{}: {} - {}",
                name, version, status, error_text
            );

            anyhow::bail!("Manifest retrieval failed: {} - {}", status, error_text)
        }
    }

    /// Download blob from registry
    async fn download_blob(&self, name: &str, digest: &str) -> Result<Vec<u8>> {
        debug!("Downloading blob: {} ({})", name, digest);

        let url = format!(
            "{}/v2/{}/{}/blobs/{}",
            self.registry_url, self.namespace, name, digest
        );

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .context("Failed to send blob request")?;

        if response.status().is_success() {
            let bytes = response.bytes().await.context("Failed to download blob")?;

            debug!("Downloaded {} bytes for blob {}", bytes.len(), digest);
            Ok(bytes.to_vec())
        } else {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());

            error!(
                "Failed to download blob {}: {} - {}",
                digest, status, error_text
            );

            anyhow::bail!("Blob download failed: {} - {}", status, error_text)
        }
    }

    /// List artifacts in repository
    pub async fn list_artifacts(&self, repository: &str) -> Result<Vec<OciArtifact>> {
        debug!("Listing artifacts in repository: {}", repository);

        let url = format!(
            "{}/v2/{}/{}/tags/list",
            self.registry_url, self.namespace, repository
        );

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .context("Failed to send tags list request")?;

        if response.status().is_success() {
            #[derive(Deserialize)]
            struct TagsList {
                tags: Vec<String>,
            }

            let tags_list: TagsList = response.json().await.context("Failed to parse tags list")?;

            // Get metadata for each tag
            let mut artifacts = Vec::new();

            for tag in tags_list.tags {
                if let Ok(manifest) = self.get_manifest(repository, &tag).await {
                    let artifact = OciArtifact {
                        name: repository.to_string(),
                        version: tag,
                        digest: manifest.config.digest,
                        size: manifest.config.size,
                        media_type: manifest.media_type,
                        created_at: chrono::Utc::now(),
                    };

                    artifacts.push(artifact);
                }
            }

            debug!("Found {} artifacts in {}", artifacts.len(), repository);
            Ok(artifacts)
        } else {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());

            error!(
                "Failed to list artifacts in {}: {} - {}",
                repository, status, error_text
            );

            anyhow::bail!("Artifact listing failed: {} - {}", status, error_text)
        }
    }

    /// Check if artifact exists
    pub async fn artifact_exists(&self, name: &str, version: &str) -> Result<bool> {
        debug!("Checking if artifact exists: {}:{}", name, version);

        let url = format!(
            "{}/v2/{}/{}/manifests/{}",
            self.registry_url, self.namespace, name, version
        );

        match self
            .client
            .head(&url)
            .header("Accept", "application/vnd.oci.image.manifest.v1+json")
            .send()
            .await
        {
            Ok(response) => {
                let exists = response.status().is_success();
                debug!("Artifact {}:{} exists: {}", name, version, exists);
                Ok(exists)
            }
            Err(e) => {
                error!("Failed to check artifact existence: {}", e);
                Ok(false)
            }
        }
    }

    /// Registry health check
    pub async fn health_check(&self) -> Result<bool> {
        debug!("Checking OCI registry health");

        let url = format!("{}/v2/", self.registry_url);

        match self.client.get(&url).send().await {
            Ok(response) => {
                let is_healthy = response.status().is_success();
                debug!("OCI registry health check: {}", is_healthy);
                Ok(is_healthy)
            }
            Err(e) => {
                error!("OCI registry health check failed: {}", e);
                Ok(false)
            }
        }
    }
}

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

    #[test]
    fn test_oci_client_creation() {
        let client = OciClient::new(
            "http://localhost:5000".to_string(),
            "provisioning-extensions".to_string(),
        );

        assert_eq!(client.registry_url, "http://localhost:5000");
        assert_eq!(client.namespace, "provisioning-extensions");
    }

    #[test]
    fn test_oci_artifact_creation() {
        let artifact = OciArtifact {
            name: "test-package".to_string(),
            version: "1.0.0".to_string(),
            digest: "sha256:abcdef".to_string(),
            size: 1024,
            media_type: "application/vnd.oci.image.manifest.v1+json".to_string(),
            created_at: chrono::Utc::now(),
        };

        assert_eq!(artifact.name, "test-package");
        assert_eq!(artifact.size, 1024);
    }
}
