use std::time::Duration;

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

use crate::client::traits::{
    BackendType, ExtensionClient, ReleaseAsset, ReleaseInfo, SourceClient,
};
use crate::config::GiteaConfig;
use crate::error::{RegistryError, Result};
use crate::gitea::models::{GiteaRelease, GiteaRepository};
use crate::models::{Extension, ExtensionSource, ExtensionType, ExtensionVersion};

/// Gitea API client
pub struct GiteaClient {
    backend_id: String,
    base_url: Url,
    organization: String,
    token: String,
    client: Client,
}

impl GiteaClient {
    /// Create new Gitea client with a pre-resolved token.
    ///
    /// Token resolution (file read or vault:// fetch) is the caller's
    /// responsibility. Use `ClientFactory::create_from_config_async` for
    /// automatic resolution.
    pub fn new(config: &GiteaConfig, token: String) -> Result<Self> {
        let base_url = Url::parse(&config.url)
            .map_err(|e| RegistryError::Config(format!("Invalid Gitea URL: {}", e)))?;

        let client = Client::builder()
            .timeout(Duration::from_secs(config.timeout_seconds))
            .danger_accept_invalid_certs(!config.verify_ssl)
            .build()
            .map_err(|e| RegistryError::Gitea(format!("Failed to create HTTP client: {}", e)))?;

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

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

    /// List all extensions from organization repositories
    pub async fn list_extensions(
        &self,
        extension_type: Option<ExtensionType>,
    ) -> Result<Vec<Extension>> {
        debug!(
            "Fetching repositories for organization: {}",
            self.organization
        );

        let repos = self.list_repositories().await?;
        let mut extensions = Vec::new();

        for repo in repos {
            // Skip archived repositories
            if repo.archived {
                continue;
            }

            // Parse extension type from repository name prefix
            let Some(ext_type) = self.parse_extension_type(&repo.name) else {
                continue;
            };

            // Filter by type if specified
            if let Some(filter_type) = extension_type {
                if ext_type != filter_type {
                    continue;
                }
            }

            // Get latest release for this repository
            let Ok(releases) = self.list_releases(&repo.name).await else {
                continue;
            };

            if let Some(latest_release) = releases.first() {
                if let Some(extension) = self.release_to_extension(&repo, latest_release, ext_type)
                {
                    extensions.push(extension);
                }
            }
        }

        Ok(extensions)
    }

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

        let repo = self.get_repository(&repo_name).await?;
        let releases = self.list_releases(&repo_name).await?;

        let latest_release = releases.first().ok_or_else(|| {
            RegistryError::NotFound(format!("No releases found for {}", repo_name))
        })?;

        self.release_to_extension(&repo, latest_release, extension_type)
            .ok_or_else(|| {
                RegistryError::NotFound(format!("Invalid extension metadata for {}", repo_name))
            })
    }

    /// List all versions for an extension
    pub 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 versions for: {}", repo_name);

        let releases = self.list_releases(&repo_name).await?;

        Ok(releases
            .iter()
            .map(|release| ExtensionVersion {
                version: release.tag_name.clone(),
                published_at: release.published_at.unwrap_or(release.created_at),
                download_url: release
                    .assets
                    .first()
                    .map(|a| a.browser_download_url.clone()),
                checksum: None,
                size: release.assets.first().map(|a| a.size),
            })
            .collect())
    }

    /// Download extension asset
    pub 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 extension: {} version {}", repo_name, version);

        let release = self.get_release(&repo_name, version).await?;

        let asset = release.assets.first().ok_or_else(|| {
            RegistryError::NotFound(format!("No assets found for release {}", version))
        })?;

        self.download_asset(&asset.browser_download_url).await
    }

    /// List repositories in organization
    async fn list_repositories(&self) -> Result<Vec<GiteaRepository>> {
        let url = self
            .base_url
            .join(&format!("api/v1/orgs/{}/repos", self.organization))
            .map_err(|e| RegistryError::Gitea(format!("Invalid URL: {}", e)))?;

        let response = self
            .client
            .get(url)
            .header("Authorization", format!("token {}", self.token))
            .send()
            .await
            .map_err(|e| RegistryError::Gitea(format!("Request failed: {}", e)))?;

        if !response.status().is_success() {
            return Err(RegistryError::Gitea(format!(
                "Failed to list repositories: {}",
                response.status()
            )));
        }

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

    /// Get specific repository
    async fn get_repository(&self, repo_name: &str) -> Result<GiteaRepository> {
        let url = self
            .base_url
            .join(&format!("api/v1/repos/{}/{}", self.organization, repo_name))
            .map_err(|e| RegistryError::Gitea(format!("Invalid URL: {}", e)))?;

        let response = self
            .client
            .get(url)
            .header("Authorization", format!("token {}", self.token))
            .send()
            .await
            .map_err(|e| RegistryError::Gitea(format!("Request failed: {}", e)))?;

        if response.status().as_u16() == 404 {
            return Err(RegistryError::NotFound(format!(
                "Repository not found: {}",
                repo_name
            )));
        }

        if !response.status().is_success() {
            return Err(RegistryError::Gitea(format!(
                "Failed to get repository: {}",
                response.status()
            )));
        }

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

    /// List releases for repository
    async fn list_releases(&self, repo_name: &str) -> Result<Vec<GiteaRelease>> {
        let url = self
            .base_url
            .join(&format!(
                "api/v1/repos/{}/{}/releases",
                self.organization, repo_name
            ))
            .map_err(|e| RegistryError::Gitea(format!("Invalid URL: {}", e)))?;

        let response = self
            .client
            .get(url)
            .header("Authorization", format!("token {}", self.token))
            .send()
            .await
            .map_err(|e| RegistryError::Gitea(format!("Request failed: {}", e)))?;

        if !response.status().is_success() {
            return Err(RegistryError::Gitea(format!(
                "Failed to list releases: {}",
                response.status()
            )));
        }

        let mut releases: Vec<GiteaRelease> = response
            .json()
            .await
            .map_err(|e| RegistryError::Gitea(format!("Failed to parse response: {}", e)))?;

        // Filter out drafts and prereleases, sort by date
        releases.retain(|r| !r.draft && !r.prerelease);
        releases.sort_by(|a, b| {
            let a_date = a.published_at.unwrap_or(a.created_at);
            let b_date = b.published_at.unwrap_or(b.created_at);
            b_date.cmp(&a_date)
        });

        Ok(releases)
    }

    /// Get specific release
    async fn get_release(&self, repo_name: &str, tag: &str) -> Result<GiteaRelease> {
        let url = self
            .base_url
            .join(&format!(
                "api/v1/repos/{}/{}/releases/tags/{}",
                self.organization, repo_name, tag
            ))
            .map_err(|e| RegistryError::Gitea(format!("Invalid URL: {}", e)))?;

        let response = self
            .client
            .get(url)
            .header("Authorization", format!("token {}", self.token))
            .send()
            .await
            .map_err(|e| RegistryError::Gitea(format!("Request failed: {}", e)))?;

        if response.status().as_u16() == 404 {
            return Err(RegistryError::NotFound(format!(
                "Release not found: {}",
                tag
            )));
        }

        if !response.status().is_success() {
            return Err(RegistryError::Gitea(format!(
                "Failed to get release: {}",
                response.status()
            )));
        }

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

    /// Download asset from URL
    async fn download_asset(&self, url: &str) -> Result<Bytes> {
        let response = self
            .client
            .get(url)
            .header("Authorization", format!("token {}", self.token))
            .send()
            .await
            .map_err(|e| RegistryError::Gitea(format!("Download failed: {}", e)))?;

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

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

    /// Parse extension type from repository name
    fn parse_extension_type(&self, repo_name: &str) -> Option<ExtensionType> {
        if repo_name.ends_with("_prov") {
            Some(ExtensionType::Provider)
        } else if repo_name.ends_with("_taskserv") {
            Some(ExtensionType::Taskserv)
        } else if repo_name.ends_with("_cluster") {
            Some(ExtensionType::Cluster)
        } else {
            None
        }
    }

    /// 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)
    }

    /// Convert Gitea release to Extension
    fn release_to_extension(
        &self,
        repo: &GiteaRepository,
        release: &GiteaRelease,
        extension_type: ExtensionType,
    ) -> Option<Extension> {
        let name = self.extract_extension_name(&repo.name, extension_type)?;

        Some(Extension {
            name,
            extension_type,
            version: release.tag_name.clone(),
            description: repo.description.clone().unwrap_or_default(),
            author: Some(repo.owner.login.clone()),
            repository: Some(repo.html_url.clone()),
            source: ExtensionSource::Gitea,
            published_at: release.published_at.unwrap_or(release.created_at),
            download_url: release
                .assets
                .first()
                .map(|a| a.browser_download_url.clone()),
            checksum: None,
            size: release.assets.first().map(|a| a.size),
            tags: None,
        })
    }

    /// Extract extension name from repository name
    fn extract_extension_name(
        &self,
        repo_name: &str,
        extension_type: ExtensionType,
    ) -> Option<String> {
        let suffix = match extension_type {
            ExtensionType::Provider => "_prov",
            ExtensionType::Taskserv => "_taskserv",
            ExtensionType::Cluster => "_cluster",
        };

        repo_name.strip_suffix(suffix).map(|s| s.to_string())
    }

    /// Check health of Gitea connection
    pub async fn health_check(&self) -> Result<()> {
        let url = self
            .base_url
            .join("api/v1/version")
            .map_err(|e| RegistryError::Gitea(format!("Invalid URL: {}", e)))?;

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

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

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

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

    async fn list_extensions(
        &self,
        extension_type: Option<ExtensionType>,
    ) -> Result<Vec<Extension>> {
        GiteaClient::list_extensions(self, extension_type).await
    }

    async fn get_extension(&self, extension_type: ExtensionType, name: &str) -> Result<Extension> {
        GiteaClient::get_extension(self, extension_type, name).await
    }

    async fn list_versions(
        &self,
        extension_type: ExtensionType,
        name: &str,
    ) -> Result<Vec<ExtensionVersion>> {
        GiteaClient::list_versions(self, extension_type, name).await
    }

    async fn download_extension(
        &self,
        extension_type: ExtensionType,
        name: &str,
        version: &str,
    ) -> Result<Bytes> {
        GiteaClient::download_extension(self, extension_type, name, version).await
    }

    async fn health_check(&self) -> Result<()> {
        GiteaClient::health_check(self).await
    }
}

/// SourceClient trait implementation for Gitea
#[async_trait]
impl SourceClient for GiteaClient {
    async fn get_repository_url(
        &self,
        extension_type: ExtensionType,
        name: &str,
    ) -> Result<String> {
        let repo_name = self.format_repo_name(extension_type, name);
        let repo = self.get_repository(&repo_name).await?;
        Ok(repo.html_url)
    }

    async fn list_releases(&self, repo_name: &str) -> Result<Vec<ReleaseInfo>> {
        let releases = GiteaClient::list_releases(self, 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,
            })
            .collect())
    }

    async fn get_release_notes(&self, repo_name: &str, version: &str) -> Result<String> {
        let release = self.get_release(repo_name, version).await?;
        Ok(release.body)
    }
}
