//! GitHub client implementation
//!
//! Integrates with GitHub Releases API to fetch provisioning extensions.
//! Extensions are identified as releases within repositories under a GitHub
//! organization.

use std::time::Duration;

use async_trait::async_trait;
use bytes::Bytes;
use reqwest::Client;
use tracing::debug;

use super::traits::{BackendType, ExtensionClient, ReleaseAsset, ReleaseInfo, SourceClient};
use crate::error::{RegistryError, Result};
use crate::models::{Extension, ExtensionSource, ExtensionType, ExtensionVersion};

/// GitHub configuration (reused GiteaConfig for compatibility)
pub struct GitHubClient {
    backend_id: String,
    organization: String,
    token: Option<String>,
    client: Client,
}

impl GitHubClient {
    /// Create new GitHub client with a pre-resolved token.
    ///
    /// Token resolution (file read or vault:// fetch) is the caller's
    /// responsibility. Pass `None` for unauthenticated access (lower rate
    /// limits apply).
    pub fn new(config: &crate::config::GiteaConfig, token: Option<String>) -> Result<Self> {
        let client = Client::builder()
            .timeout(Duration::from_secs(config.timeout_seconds))
            .danger_accept_invalid_certs(!config.verify_ssl)
            .user_agent("extension-registry/0.1.0")
            .build()
            .map_err(|e| RegistryError::Config(format!("Failed to create HTTP client: {}", e)))?;

        let backend_id = config
            .id
            .clone()
            .unwrap_or_else(|| format!("github-{}", config.organization));

        Ok(Self {
            backend_id,
            organization: config.organization.clone(),
            token,
            client,
        })
    }

    /// Internal: List releases from GitHub
    async fn list_releases_internal(&self, owner: &str, repo: &str) -> Result<Vec<GitHubRelease>> {
        let url = format!(
            "https://api.github.com/repos/{}/{}/releases?per_page=100",
            owner, repo
        );

        let mut request = self.client.get(&url);

        if let Some(ref token) = self.token {
            request = request.header("Authorization", format!("token {}", token));
        }

        let response = request
            .send()
            .await
            .map_err(|e| RegistryError::Config(format!("GitHub API request failed: {}", e)))?;

        if !response.status().is_success() {
            return Err(RegistryError::Config(format!(
                "GitHub API returned: {}",
                response.status()
            )));
        }

        response
            .json()
            .await
            .map_err(|e| RegistryError::Config(format!("Failed to parse GitHub response: {}", e)))
    }

    /// Internal: Format repository name from extension type and name
    fn format_repo_name(&self, extension_type: ExtensionType, name: &str) -> String {
        let suffix = match extension_type {
            ExtensionType::Provider => "_prov",
            ExtensionType::Taskserv => "_taskserv",
            ExtensionType::Cluster => "_cluster",
        };
        format!("{}{}", name, suffix)
    }
}

/// GitHub release (minimal fields)
#[derive(Debug, Clone, serde::Deserialize)]
pub struct GitHubRelease {
    pub tag_name: String,
    pub name: String,
    pub body: Option<String>,
    pub draft: bool,
    pub prerelease: bool,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub published_at: Option<chrono::DateTime<chrono::Utc>>,
    pub assets: Vec<GitHubAsset>,
}

/// GitHub release asset
#[derive(Debug, Clone, serde::Deserialize)]
pub struct GitHubAsset {
    pub name: String,
    pub browser_download_url: String,
    pub size: u64,
}

/// ExtensionClient trait implementation for GitHub
#[async_trait]
impl ExtensionClient for GitHubClient {
    fn backend_id(&self) -> &str {
        &self.backend_id
    }

    fn backend_type(&self) -> BackendType {
        BackendType::GitHub
    }

    async fn list_extensions(
        &self,
        _extension_type: Option<ExtensionType>,
    ) -> Result<Vec<Extension>> {
        debug!(
            "Fetching repositories for GitHub organization: {}",
            self.organization
        );

        // Note: This is a simplified implementation that would require
        // listing repos from the organization. For full implementation,
        // integrate GitHub's organizations API endpoint.
        Ok(Vec::new())
    }

    async fn get_extension(&self, extension_type: ExtensionType, name: &str) -> Result<Extension> {
        let repo_name = self.format_repo_name(extension_type, name);
        debug!(
            "Fetching GitHub extension: {}/{}",
            self.organization, repo_name
        );

        let releases = self
            .list_releases_internal(&self.organization, &repo_name)
            .await?;

        let latest = releases
            .iter()
            .find(|r| !r.draft && !r.prerelease)
            .ok_or_else(|| {
                RegistryError::NotFound(format!(
                    "No releases found for {}/{}",
                    self.organization, repo_name
                ))
            })?;

        let asset = latest.assets.first().ok_or_else(|| {
            RegistryError::NotFound(format!(
                "No assets found in latest release of {}/{}",
                self.organization, repo_name
            ))
        })?;

        Ok(Extension {
            name: name.to_string(),
            extension_type,
            version: latest.tag_name.clone(),
            description: latest.body.clone().unwrap_or_default(),
            author: None,
            repository: Some(format!(
                "https://github.com/{}/{}",
                self.organization, repo_name
            )),
            source: ExtensionSource::Github,
            published_at: latest.published_at.unwrap_or(latest.created_at),
            download_url: Some(asset.browser_download_url.clone()),
            checksum: None,
            size: Some(asset.size),
            tags: None,
        })
    }

    async fn list_versions(
        &self,
        extension_type: ExtensionType,
        name: &str,
    ) -> Result<Vec<ExtensionVersion>> {
        let repo_name = self.format_repo_name(extension_type, name);
        debug!(
            "Fetching GitHub versions for: {}/{}",
            self.organization, repo_name
        );

        let releases = self
            .list_releases_internal(&self.organization, &repo_name)
            .await?;

        Ok(releases
            .into_iter()
            .filter(|r| !r.draft && !r.prerelease)
            .map(|r| ExtensionVersion {
                version: r.tag_name,
                published_at: r.published_at.unwrap_or(r.created_at),
                download_url: r.assets.first().map(|a| a.browser_download_url.clone()),
                checksum: None,
                size: r.assets.first().map(|a| a.size),
            })
            .collect())
    }

    async fn download_extension(
        &self,
        extension_type: ExtensionType,
        name: &str,
        version: &str,
    ) -> Result<Bytes> {
        let repo_name = self.format_repo_name(extension_type, name);
        debug!(
            "Downloading GitHub extension: {}/{} version {}",
            self.organization, repo_name, version
        );

        let releases = self
            .list_releases_internal(&self.organization, &repo_name)
            .await?;

        let release = releases
            .iter()
            .find(|r| r.tag_name == version)
            .ok_or_else(|| {
                RegistryError::NotFound(format!(
                    "Release {} not found for {}/{}",
                    version, self.organization, repo_name
                ))
            })?;

        let asset = release.assets.first().ok_or_else(|| {
            RegistryError::NotFound(format!(
                "No assets in release {} of {}/{}",
                version, self.organization, repo_name
            ))
        })?;

        let response = self
            .client
            .get(&asset.browser_download_url)
            .send()
            .await
            .map_err(|e| RegistryError::Config(format!("Download failed: {}", e)))?;

        if !response.status().is_success() {
            return Err(RegistryError::Config(format!(
                "Download failed: {}",
                response.status()
            )));
        }

        response
            .bytes()
            .await
            .map_err(|e| RegistryError::Config(format!("Failed to read response: {}", e)))
    }

    async fn health_check(&self) -> Result<()> {
        let url = "https://api.github.com/zen";

        let response = self
            .client
            .get(url)
            .timeout(Duration::from_secs(5))
            .send()
            .await
            .map_err(|e| RegistryError::Config(format!("GitHub health check failed: {}", e)))?;

        if response.status().is_success() {
            Ok(())
        } else {
            Err(RegistryError::Config(format!(
                "GitHub health check returned: {}",
                response.status()
            )))
        }
    }
}

/// SourceClient trait implementation for GitHub
#[async_trait]
impl SourceClient for GitHubClient {
    async fn get_repository_url(
        &self,
        extension_type: ExtensionType,
        name: &str,
    ) -> Result<String> {
        let repo_name = self.format_repo_name(extension_type, name);
        Ok(format!(
            "https://github.com/{}/{}",
            self.organization, repo_name
        ))
    }

    async fn list_releases(&self, repo_name: &str) -> Result<Vec<ReleaseInfo>> {
        let releases = self
            .list_releases_internal(&self.organization, repo_name)
            .await?;
        Ok(releases
            .into_iter()
            .map(|r| ReleaseInfo {
                tag: r.tag_name,
                created_at: r.created_at,
                published_at: r.published_at,
                assets: r
                    .assets
                    .into_iter()
                    .map(|a| ReleaseAsset {
                        name: a.name,
                        download_url: a.browser_download_url,
                        size: Some(a.size),
                    })
                    .collect(),
                description: r.body.unwrap_or_default(),
            })
            .collect())
    }

    async fn get_release_notes(&self, repo_name: &str, version: &str) -> Result<String> {
        let releases = self
            .list_releases_internal(&self.organization, repo_name)
            .await?;
        let release = releases
            .iter()
            .find(|r| r.tag_name == version)
            .ok_or_else(|| {
                RegistryError::NotFound(format!(
                    "Release {} not found in {}/{}",
                    version, self.organization, repo_name
                ))
            })?;
        Ok(release.body.clone().unwrap_or_default())
    }
}
