//! Authentication provider trait and implementations
//!
//! Defines a unified interface for different authentication methods.

use async_trait::async_trait;
use std::sync::Arc;

use iam_types::{
    AuthMethod, Error, IamError, InternalTokenClaims, JwtClaims, PrincipalRef, Result,
};

use crate::jwt::JwtVerifier;
use crate::mtls::{CertificateInfo, MtlsVerifier};
use crate::token::InternalTokenService;

/// Result of authentication
#[derive(Debug, Clone)]
pub struct AuthnResult {
    /// Authenticated principal reference
    pub principal_ref: PrincipalRef,
    /// Authentication method used
    pub auth_method: AuthMethod,
    /// Node ID (for service accounts)
    pub node_id: Option<String>,
    /// Organization ID
    pub org_id: Option<String>,
    /// Project ID
    pub project_id: Option<String>,
    /// Groups (from JWT)
    pub groups: Vec<String>,
    /// Original claims (if JWT)
    pub jwt_claims: Option<JwtClaims>,
    /// Internal token claims (if internal token)
    pub internal_claims: Option<InternalTokenClaims>,
}

/// Authentication credentials
#[derive(Debug, Clone)]
pub enum AuthnCredentials {
    /// Bearer token (JWT or internal token)
    BearerToken(String),
    /// mTLS certificate info
    Certificate(CertificateInfo),
}

/// Authentication provider trait
#[async_trait]
pub trait AuthnProvider: Send + Sync {
    /// Authenticate using the provided credentials
    async fn authenticate(&self, credentials: &AuthnCredentials) -> Result<AuthnResult>;
}

/// Combined authentication provider supporting multiple methods
pub struct CombinedAuthProvider {
    jwt_verifier: Option<Arc<JwtVerifier>>,
    mtls_verifier: Option<Arc<MtlsVerifier>>,
    internal_token_service: Option<Arc<InternalTokenService>>,
}

impl CombinedAuthProvider {
    /// Create a new combined auth provider
    pub fn new() -> Self {
        Self {
            jwt_verifier: None,
            mtls_verifier: None,
            internal_token_service: None,
        }
    }

    /// Add JWT verifier
    pub fn with_jwt(mut self, verifier: JwtVerifier) -> Self {
        self.jwt_verifier = Some(Arc::new(verifier));
        self
    }

    /// Add mTLS verifier
    pub fn with_mtls(mut self, verifier: MtlsVerifier) -> Self {
        self.mtls_verifier = Some(Arc::new(verifier));
        self
    }

    /// Add internal token service
    pub fn with_internal_token(mut self, service: InternalTokenService) -> Self {
        self.internal_token_service = Some(Arc::new(service));
        self
    }

    /// Authenticate a bearer token
    async fn authenticate_bearer(&self, token: &str) -> Result<AuthnResult> {
        // Try internal token first (faster, local verification)
        if let Some(internal_service) = &self.internal_token_service {
            if let Ok(claims) = internal_service.verify(token).await {
                return Ok(AuthnResult {
                    principal_ref: PrincipalRef::new(
                        claims.principal_kind.clone(),
                        &claims.principal_id,
                    ),
                    auth_method: AuthMethod::Internal,
                    node_id: claims.node_id.clone(),
                    org_id: claims.org_id.clone(),
                    project_id: claims.project_id.clone(),
                    groups: vec![],
                    jwt_claims: None,
                    internal_claims: Some(claims),
                });
            }
        }

        // Try JWT verification
        if let Some(jwt_verifier) = &self.jwt_verifier {
            let claims = jwt_verifier.verify(token).await?;

            // Map JWT sub to principal
            let principal_ref = PrincipalRef::user(&claims.sub);

            return Ok(AuthnResult {
                principal_ref,
                auth_method: AuthMethod::Jwt,
                node_id: None,
                org_id: claims.org_id.clone(),
                project_id: claims.project_id.clone(),
                groups: claims.groups.clone(),
                jwt_claims: Some(claims),
                internal_claims: None,
            });
        }

        Err(Error::Iam(IamError::AuthnFailed(
            "No authentication provider configured for bearer tokens".into(),
        )))
    }

    /// Authenticate using certificate
    fn authenticate_certificate(&self, cert_info: &CertificateInfo) -> Result<AuthnResult> {
        let mtls_verifier = self.mtls_verifier.as_ref().ok_or_else(|| {
            Error::Iam(IamError::AuthnFailed(
                "mTLS authentication not configured".into(),
            ))
        })?;

        let result = mtls_verifier.verify(cert_info)?;

        Ok(AuthnResult {
            principal_ref: result.principal_ref,
            auth_method: AuthMethod::Mtls,
            node_id: result.node_id,
            org_id: None,
            project_id: None,
            groups: vec![],
            jwt_claims: None,
            internal_claims: None,
        })
    }
}

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

#[async_trait]
impl AuthnProvider for CombinedAuthProvider {
    async fn authenticate(&self, credentials: &AuthnCredentials) -> Result<AuthnResult> {
        match credentials {
            AuthnCredentials::BearerToken(token) => self.authenticate_bearer(token).await,
            AuthnCredentials::Certificate(cert_info) => self.authenticate_certificate(cert_info),
        }
    }
}

/// Extract authentication credentials from HTTP headers
pub fn extract_credentials_from_headers(
    authorization: Option<&str>,
    cert_info: Option<CertificateInfo>,
) -> Option<AuthnCredentials> {
    extract_credentials_from_headers_with_photon_token(authorization, None, cert_info)
}

/// Extract authentication credentials from HTTP headers (supports legacy custom token header).
pub fn extract_credentials_from_headers_with_photon_token(
    authorization: Option<&str>,
    photon_auth_token: Option<&str>,
    cert_info: Option<CertificateInfo>,
) -> Option<AuthnCredentials> {
    // Check for mTLS first (if certificate is provided)
    if let Some(cert) = cert_info {
        return Some(AuthnCredentials::Certificate(cert));
    }

    // Check Authorization header
    if let Some(auth_header) = authorization {
        if let Some(creds) = parse_scheme_credentials(auth_header) {
            return Some(creds);
        }
    }

    // Fallback: legacy token header used by some clients/proxies.
    if let Some(token_value) = photon_auth_token {
        let trimmed = token_value.trim();
        if trimmed.is_empty() {
            return None;
        }
        if let Some(creds) = parse_scheme_credentials(trimmed) {
            return Some(creds);
        }
        return Some(AuthnCredentials::BearerToken(trimmed.to_string()));
    }

    None
}

fn parse_scheme_credentials(value: &str) -> Option<AuthnCredentials> {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        return None;
    }

    let mut parts = trimmed.split_whitespace();
    let scheme = parts.next()?;
    let token = parts.next()?;
    if parts.next().is_some() {
        return None;
    }

    if scheme.eq_ignore_ascii_case("bearer") {
        return Some(AuthnCredentials::BearerToken(token.to_string()));
    }
    None
}

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

    #[tokio::test]
    async fn test_combined_provider_no_config() {
        let provider = CombinedAuthProvider::new();

        let result = provider
            .authenticate(&AuthnCredentials::BearerToken("some-token".into()))
            .await;

        assert!(result.is_err());
    }

    #[test]
    fn test_extract_bearer_token() {
        let creds = extract_credentials_from_headers(Some("Bearer abc123"), None);

        assert!(matches!(
            creds,
            Some(AuthnCredentials::BearerToken(t)) if t == "abc123"
        ));
    }

    #[test]
    fn test_extract_bearer_token_case_insensitive() {
        let creds = extract_credentials_from_headers(Some("bearer abc123"), None);

        assert!(matches!(
            creds,
            Some(AuthnCredentials::BearerToken(t)) if t == "abc123"
        ));
    }

    #[test]
    fn test_extract_photon_token_header() {
        let creds = extract_credentials_from_headers_with_photon_token(None, Some("abc123"), None);

        assert!(matches!(
            creds,
            Some(AuthnCredentials::BearerToken(t)) if t == "abc123"
        ));
    }

    #[test]
    fn test_extract_photon_token_header_bearer_scheme() {
        let creds =
            extract_credentials_from_headers_with_photon_token(None, Some("Bearer abc123"), None);

        assert!(matches!(
            creds,
            Some(AuthnCredentials::BearerToken(t)) if t == "abc123"
        ));
    }
}
