//! Authentication and authorization.

use crate::error::ApiError;
use axum::{
    extract::FromRequestParts,
    http::{header::AUTHORIZATION, request::Parts, HeaderMap},
};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use chrono::{DateTime, Duration, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

/// API key tier for rate limiting.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ApiKeyTier {
    Free,
    Developer,
    Pro,
    Enterprise,
}

impl Default for ApiKeyTier {
    fn default() -> Self {
        Self::Free
    }
}

/// Permissions for API operations.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Permissions {
    /// Read access
    pub read: bool,

    /// Write access
    pub write: bool,

    /// Delete access
    pub delete: bool,

    /// Admin access
    pub admin: bool,

    /// Specific service access (empty = all)
    pub services: Vec<String>,
}

impl Default for Permissions {
    fn default() -> Self {
        Self {
            read: true,
            write: false,
            delete: false,
            admin: false,
            services: vec![],
        }
    }
}

impl Permissions {
    /// Full permissions.
    pub fn full() -> Self {
        Self {
            read: true,
            write: true,
            delete: true,
            admin: true,
            services: vec![],
        }
    }

    /// Read-only permissions.
    pub fn read_only() -> Self {
        Self {
            read: true,
            write: false,
            delete: false,
            admin: false,
            services: vec![],
        }
    }

    /// Check if the user has access to a specific service.
    pub fn has_service_access(&self, service: &str) -> bool {
        self.services.is_empty() || self.services.iter().any(|s| s == service)
    }
}

/// Authenticated user context.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthContext {
    /// User/account ID
    pub user_id: String,

    /// API key ID (if authenticated via API key)
    pub api_key_id: Option<String>,

    /// User's tier
    pub tier: ApiKeyTier,

    /// User's permissions
    pub permissions: Permissions,

    /// Authentication timestamp
    pub authenticated_at: DateTime<Utc>,

    /// Token expiration (if JWT)
    pub expires_at: Option<DateTime<Utc>>,
}

impl AuthContext {
    /// Check if the user can read.
    pub fn can_read(&self) -> bool {
        self.permissions.read
    }

    /// Check if the user can write.
    pub fn can_write(&self) -> bool {
        self.permissions.write
    }

    /// Check if the user can delete.
    pub fn can_delete(&self) -> bool {
        self.permissions.delete
    }

    /// Check if the user is admin.
    pub fn is_admin(&self) -> bool {
        self.permissions.admin
    }

    /// Check if the user can access a service.
    pub fn can_access_service(&self, service: &str) -> bool {
        self.permissions.has_service_access(service)
    }
}

/// JWT claims structure.
#[derive(Debug, Serialize, Deserialize)]
pub struct JwtClaims {
    /// Subject (user ID)
    pub sub: String,

    /// Expiration time
    pub exp: i64,

    /// Issued at
    pub iat: i64,

    /// API key ID (if applicable)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub api_key_id: Option<String>,

    /// User tier
    pub tier: ApiKeyTier,

    /// Permissions
    pub permissions: Permissions,
}

/// Authentication service.
#[derive(Clone)]
pub struct AuthService {
    jwt_secret: Arc<String>,
    jwt_expiration: Duration,
}

impl AuthService {
    /// Create a new authentication service.
    pub fn new(jwt_secret: String, jwt_expiration_secs: i64) -> Self {
        Self {
            jwt_secret: Arc::new(jwt_secret),
            jwt_expiration: Duration::seconds(jwt_expiration_secs),
        }
    }

    /// Create from configuration.
    pub fn from_config(config: crate::config::AuthConfig) -> Self {
        Self::new(config.jwt_secret, config.jwt_expiration.as_secs() as i64)
    }

    /// Generate a JWT token.
    pub fn generate_token(&self, user_id: &str, tier: ApiKeyTier, permissions: Permissions) -> Result<String, ApiError> {
        let now = Utc::now();
        let exp = now + self.jwt_expiration;

        let claims = JwtClaims {
            sub: user_id.to_string(),
            exp: exp.timestamp(),
            iat: now.timestamp(),
            api_key_id: None,
            tier,
            permissions,
        };

        encode(
            &Header::default(),
            &claims,
            &EncodingKey::from_secret(self.jwt_secret.as_bytes()),
        )
        .map_err(|e| ApiError::InternalError)
    }

    /// Validate a JWT token.
    pub fn validate_token(&self, token: &str) -> Result<AuthContext, ApiError> {
        let token_data = decode::<JwtClaims>(
            token,
            &DecodingKey::from_secret(self.jwt_secret.as_bytes()),
            &Validation::default(),
        )
        .map_err(|e| match e.kind() {
            jsonwebtoken::errors::ErrorKind::ExpiredSignature => ApiError::ExpiredToken,
            _ => ApiError::InvalidToken(e.to_string()),
        })?;

        let claims = token_data.claims;
        let expires_at = DateTime::from_timestamp(claims.exp, 0)
            .map(|dt| dt.with_timezone(&Utc));

        Ok(AuthContext {
            user_id: claims.sub,
            api_key_id: claims.api_key_id,
            tier: claims.tier,
            permissions: claims.permissions,
            authenticated_at: Utc::now(),
            expires_at,
        })
    }

    /// Validate an API key.
    pub async fn validate_api_key(&self, api_key: &str) -> Result<AuthContext, ApiError> {
        // Parse API key format: key_id:secret
        let parts: Vec<&str> = api_key.split(':').collect();
        if parts.len() != 2 {
            return Err(ApiError::InvalidApiKey);
        }

        let key_id = parts[0];
        let secret = parts[1];

        // Hash the secret using blake3
        let secret_hash = blake3::hash(secret.as_bytes()).to_hex().to_string();

        // In production, this would look up the key in a database
        // For now, we'll accept any properly formatted key for development
        // TODO: Implement actual key lookup

        // Placeholder: accept keys starting with "sk_" for development
        if !key_id.starts_with("sk_") {
            return Err(ApiError::InvalidApiKey);
        }

        // Return a development context
        Ok(AuthContext {
            user_id: format!("user_{}", &key_id[3..]),
            api_key_id: Some(key_id.to_string()),
            tier: ApiKeyTier::Developer,
            permissions: Permissions::full(),
            authenticated_at: Utc::now(),
            expires_at: None,
        })
    }

    /// Authenticate from headers.
    pub async fn authenticate(&self, headers: &HeaderMap) -> Result<AuthContext, ApiError> {
        // Try Bearer token first
        if let Some(auth_header) = headers.get(AUTHORIZATION) {
            let auth_str = auth_header
                .to_str()
                .map_err(|_| ApiError::InvalidToken("Invalid header encoding".to_string()))?;

            if auth_str.starts_with("Bearer ") {
                let token = &auth_str[7..];
                return self.validate_token(token);
            }
        }

        // Try API key header
        if let Some(api_key) = headers.get("X-API-Key") {
            let key = api_key
                .to_str()
                .map_err(|_| ApiError::InvalidApiKey)?;
            return self.validate_api_key(key).await;
        }

        // Try Authorization: Basic (for API key as base64)
        if let Some(auth_header) = headers.get(AUTHORIZATION) {
            let auth_str = auth_header
                .to_str()
                .map_err(|_| ApiError::InvalidToken("Invalid header encoding".to_string()))?;

            if auth_str.starts_with("Basic ") {
                let encoded = &auth_str[6..];
                let decoded = BASE64
                    .decode(encoded)
                    .map_err(|_| ApiError::InvalidApiKey)?;
                let key = String::from_utf8(decoded)
                    .map_err(|_| ApiError::InvalidApiKey)?;
                return self.validate_api_key(&key).await;
            }
        }

        Err(ApiError::Unauthorized)
    }
}

/// Extractor for authenticated requests.
#[derive(Debug, Clone)]
pub struct Authenticated(pub AuthContext);

impl<S> FromRequestParts<S> for Authenticated
where
    S: Send + Sync,
{
    type Rejection = ApiError;

    fn from_request_parts<'life0, 'life1, 'async_trait>(
        parts: &'life0 mut Parts,
        _state: &'life1 S,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self, Self::Rejection>> + Send + 'async_trait>>
    where
        'life0: 'async_trait,
        'life1: 'async_trait,
        Self: 'async_trait,
    {
        Box::pin(async move {
            // Get auth service from extensions
            let auth_service = parts
                .extensions
                .get::<AuthService>()
                .ok_or(ApiError::InternalError)?
                .clone();

            let context = auth_service.authenticate(&parts.headers).await?;
            Ok(Authenticated(context))
        })
    }
}

/// Optional authentication extractor.
#[derive(Debug, Clone)]
pub struct OptionalAuth(pub Option<AuthContext>);

impl<S> FromRequestParts<S> for OptionalAuth
where
    S: Send + Sync,
{
    type Rejection = ApiError;

    fn from_request_parts<'life0, 'life1, 'async_trait>(
        parts: &'life0 mut Parts,
        _state: &'life1 S,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self, Self::Rejection>> + Send + 'async_trait>>
    where
        'life0: 'async_trait,
        'life1: 'async_trait,
        Self: 'async_trait,
    {
        Box::pin(async move {
            // Get auth service from extensions
            let auth_service = parts
                .extensions
                .get::<AuthService>()
                .cloned();

            if let Some(auth_service) = auth_service {
                match auth_service.authenticate(&parts.headers).await {
                    Ok(context) => Ok(OptionalAuth(Some(context))),
                    Err(_) => Ok(OptionalAuth(None)),
                }
            } else {
                Ok(OptionalAuth(None))
            }
        })
    }
}

/// Require specific permissions.
pub fn require_permission(
    context: &AuthContext,
    permission: &str,
) -> Result<(), ApiError> {
    let has_permission = match permission {
        "read" => context.can_read(),
        "write" => context.can_write(),
        "delete" => context.can_delete(),
        "admin" => context.is_admin(),
        _ => false,
    };

    if has_permission {
        Ok(())
    } else {
        Err(ApiError::InsufficientPermissions)
    }
}

/// Require access to a specific service.
pub fn require_service_access(
    context: &AuthContext,
    service: &str,
) -> Result<(), ApiError> {
    if context.can_access_service(service) {
        Ok(())
    } else {
        Err(ApiError::Forbidden(format!(
            "No access to service: {}",
            service
        )))
    }
}
