//! Trust registry HTTP client implementation.

// WASM is single-threaded, so futures don't need to be Send
#![cfg_attr(target_arch = "wasm32", allow(clippy::future_not_send))]

use std::time::Duration;

use async_trait::async_trait;
#[cfg(feature = "tracing")]
use tracing::debug;

use super::{IssuerInfo, TrustRegistry, TrustRegistryCache};
use crate::config::{CacheConfig, RetryPolicy};
use crate::constants::DEFAULT_RETRY_AFTER_SECONDS;
#[cfg(not(target_arch = "wasm32"))]
use crate::constants::DEFAULT_TIMEOUT_SECONDS;
use crate::error::{HttpError, TrustRegistryError};

/// Production trust registry client with caching.
pub struct TrustRegistryClient {
    endpoint: String,
    http_client: reqwest::Client,
    cache: TrustRegistryCache,
    retry_policy: RetryPolicy,
}

impl TrustRegistryClient {
    /// Create a new trust registry client.
    ///
    /// # Arguments
    ///
    /// * `endpoint` - Trust registry API endpoint (e.g., `https://human.lastid.co/v1/trust-registry`)
    /// * `cache_config` - Cache configuration
    /// * `retry_policy` - Retry policy for failed requests
    pub fn new(
        endpoint: String,
        cache_config: &CacheConfig,
        retry_policy: RetryPolicy,
    ) -> Result<Self, HttpError> {
        let builder = reqwest::Client::builder();

        // Timeout only available on native (WASM uses browser's fetch timeout)
        #[cfg(not(target_arch = "wasm32"))]
        let builder = builder.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECONDS));

        let http_client = builder
            .build()
            .map_err(|e| HttpError::network(e.to_string()))?;

        let cache = if cache_config.enabled {
            TrustRegistryCache::new(
                Duration::from_secs(cache_config.ttl_seconds),
                cache_config.max_entries,
            )
        } else {
            // Use a very short TTL effectively disabling caching
            TrustRegistryCache::new(Duration::from_millis(1), 1)
        };

        Ok(Self {
            endpoint,
            http_client,
            cache,
            retry_policy,
        })
    }

    /// Fetch issuer from API (cache miss).
    #[allow(
        clippy::cast_precision_loss,
        clippy::cast_possible_truncation,
        clippy::cast_sign_loss
    )]
    async fn fetch_issuer(&self, did: &str) -> Result<IssuerInfo, TrustRegistryError> {
        let url = format!("{}/issuers/{}", self.endpoint, urlencoding::encode(did));

        let mut last_error = None;
        let mut delay = Duration::from_millis(self.retry_policy.initial_delay_ms);

        for attempt in 0..self.retry_policy.max_attempts {
            // Build the GET request
            let request = self.http_client.get(&url);

            // In WASM, explicitly omit credentials to avoid CORS issues.
            // The trust registry is a public endpoint that doesn't require authentication.
            #[cfg(target_arch = "wasm32")]
            let request = request.fetch_credentials_omit();

            match request.send().await {
                Ok(response) => {
                    let status = response.status();

                    if status.is_success() {
                        let issuer = response
                            .json::<IssuerInfo>()
                            .await
                            .map_err(|e| HttpError::network(e.to_string()))?;
                        return Ok(issuer);
                    }

                    if status.as_u16() == 404 {
                        return Err(TrustRegistryError::issuer_not_found(did));
                    }

                    if status.as_u16() == 429 {
                        // Parse Retry-After header
                        let retry_after = response
                            .headers()
                            .get("Retry-After")
                            .and_then(|v| v.to_str().ok())
                            .and_then(|s| s.parse().ok())
                            .unwrap_or(DEFAULT_RETRY_AFTER_SECONDS);

                        last_error = Some(TrustRegistryError::Http(HttpError::rate_limited(
                            retry_after,
                        )));

                        #[cfg(not(target_arch = "wasm32"))]
                        tokio::time::sleep(Duration::from_secs(retry_after)).await;

                        continue;
                    }

                    let message = response.text().await.unwrap_or_default();
                    let http_error = HttpError::status(status.as_u16(), message);

                    if !http_error.is_retryable() {
                        return Err(TrustRegistryError::Http(http_error));
                    }

                    last_error = Some(TrustRegistryError::Http(http_error));
                }
                Err(e) => {
                    if e.is_timeout() {
                        last_error = Some(TrustRegistryError::Http(HttpError::Timeout));
                    } else {
                        last_error =
                            Some(TrustRegistryError::Http(HttpError::network(e.to_string())));
                    }
                }
            }

            // Exponential backoff (skip on last attempt)
            if attempt < self.retry_policy.max_attempts - 1 {
                #[cfg(not(target_arch = "wasm32"))]
                tokio::time::sleep(delay).await;

                delay = Duration::from_millis(
                    (delay.as_millis() as f64 * self.retry_policy.backoff_base)
                        .min(self.retry_policy.max_delay_ms as f64) as u64,
                );
            }
        }

        Err(last_error
            .unwrap_or_else(|| TrustRegistryError::Http(HttpError::network("Unknown error"))))
    }
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl TrustRegistry for TrustRegistryClient {
    async fn get_issuer(&self, did: &str) -> Result<IssuerInfo, TrustRegistryError> {
        // Check cache first
        if let Some(cached) = self.cache.get(did) {
            #[cfg(feature = "tracing")]
            debug!(issuer_did = did, "Trust registry cache hit");
            return Ok(cached);
        }

        // Cache miss - fetch from API
        #[cfg(feature = "tracing")]
        debug!(
            issuer_did = did,
            "Trust registry cache miss, fetching from API"
        );

        let issuer = self.fetch_issuer(did).await?;

        // Store in cache
        self.cache.insert(did.to_string(), issuer.clone());

        Ok(issuer)
    }

    async fn invalidate_cache(&self, did: &str) {
        self.cache.invalidate(did);
    }
}

impl Clone for TrustRegistryClient {
    fn clone(&self) -> Self {
        Self {
            endpoint: self.endpoint.clone(),
            http_client: self.http_client.clone(),
            cache: self.cache.clone(),
            retry_policy: self.retry_policy.clone(),
        }
    }
}

#[cfg(all(test, not(target_arch = "wasm32")))]
pub mod mock {
    //! Mock trust registry for testing.

    use std::collections::HashMap;
    use std::sync::RwLock;

    use super::*;

    /// Mock trust registry implementation for testing.
    pub struct MockTrustRegistry {
        issuers: RwLock<HashMap<String, IssuerInfo>>,
    }

    impl MockTrustRegistry {
        /// Create a new mock trust registry.
        #[must_use]
        pub fn new() -> Self {
            Self {
                issuers: RwLock::new(HashMap::new()),
            }
        }

        /// Add an issuer to the mock registry.
        pub fn add_issuer(&self, did: impl Into<String>, info: IssuerInfo) {
            self.issuers.write().unwrap().insert(did.into(), info);
        }
    }

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

    #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
    #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
    impl TrustRegistry for MockTrustRegistry {
        async fn get_issuer(&self, did: &str) -> Result<IssuerInfo, TrustRegistryError> {
            self.issuers
                .read()
                .unwrap()
                .get(did)
                .cloned()
                .ok_or_else(|| TrustRegistryError::issuer_not_found(did))
        }

        async fn invalidate_cache(&self, _did: &str) {
            // No-op for mock
        }
    }
}

#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
    use super::mock::MockTrustRegistry;
    use super::*;
    use crate::trust_registry::{ES256PublicKey, IssuerStatus};

    fn test_public_key() -> ES256PublicKey {
        ES256PublicKey {
            kty: "EC".into(),
            crv: "P-256".into(),
            x: "WbbXWlY0Sdw6eVKiJGnFFAJnGDHVjdVRLWbZGIgn3Go".into(),
            y: "bfIuXkJHLZvVMZ96ekEqhyuSoVVbWBzWqcAzSvhp2gs".into(),
        }
    }

    #[tokio::test]
    async fn test_mock_get_issuer() {
        let mock = MockTrustRegistry::new();
        mock.add_issuer(
            "did:lastid:test",
            IssuerInfo {
                enterprise_did: "did:lastid:test".into(),
                organization_name: "Test Org".into(),
                endpoint_url: "https://test.com".into(),
                status: IssuerStatus::Authorized,
                permitted_types: vec!["LastID.Base".into()],
                public_key: test_public_key(),
            },
        );

        let result = mock.get_issuer("did:lastid:test").await;
        assert!(result.is_ok());
        assert_eq!(result.unwrap().organization_name, "Test Org");
    }

    #[tokio::test]
    async fn test_mock_issuer_not_found() {
        let mock = MockTrustRegistry::new();

        let result = mock.get_issuer("did:lastid:nonexistent").await;
        assert!(matches!(
            result.unwrap_err(),
            TrustRegistryError::IssuerNotFound(_)
        ));
    }

    #[tokio::test]
    async fn test_mock_validate_issuer_authorized() {
        let mock = MockTrustRegistry::new();
        mock.add_issuer(
            "did:lastid:test",
            IssuerInfo {
                enterprise_did: "did:lastid:test".into(),
                organization_name: "Test Org".into(),
                endpoint_url: "https://test.com".into(),
                status: IssuerStatus::Authorized,
                permitted_types: vec![],
                public_key: test_public_key(),
            },
        );

        let result = mock.validate_issuer("did:lastid:test").await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_mock_validate_issuer_suspended() {
        let mock = MockTrustRegistry::new();
        mock.add_issuer(
            "did:lastid:test",
            IssuerInfo {
                enterprise_did: "did:lastid:test".into(),
                organization_name: "Test Org".into(),
                endpoint_url: "https://test.com".into(),
                status: IssuerStatus::Suspended,
                permitted_types: vec![],
                public_key: test_public_key(),
            },
        );

        let result = mock.validate_issuer("did:lastid:test").await;
        assert!(matches!(
            result.unwrap_err(),
            TrustRegistryError::InvalidIssuerStatus { .. }
        ));
    }
}
