//! Authentication module
//!
//! Handles JWT validation for dashboard users and API key
//! validation for agents.

pub mod agent_keys;
pub mod jwt;
pub mod password;
pub mod token_blacklist;
pub mod viewer_token_registry;

pub use jwt::{Claims, JwtConfig, ViewerAccess};
pub use password::{generate_random_password, hash_password, verify_password};
pub use token_blacklist::TokenBlacklist;
pub use viewer_token_registry::ViewerTokenRegistry;

use axum::{
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
};
use std::sync::Arc;

/// Authenticated user from JWT
#[derive(Debug, Clone)]
pub struct AuthenticatedUser {
    pub user_id: String,
    pub username: String,
    pub role: String,
    #[allow(dead_code)]
    // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
    pub permissions: Vec<String>,
}

impl AuthenticatedUser {
    /// Check if user has a specific permission.
    ///
    /// Admins implicitly hold every permission. Consumed by the viewer-token
    /// authorization gate (`api::sessions::mint_viewer_token`).
    pub fn has_permission(&self, permission: &str) -> bool {
        if self.role == "admin" {
            return true;
        }
        self.permissions.contains(&permission.to_string())
    }

    /// Check if user is admin
    pub fn is_admin(&self) -> bool {
        self.role == "admin"
    }
}

impl From<Claims> for AuthenticatedUser {
    fn from(claims: Claims) -> Self {
        Self {
            user_id: claims.sub,
            username: claims.username,
            role: claims.role,
            permissions: claims.permissions,
        }
    }
}

/// Extract authenticated user from request
#[axum::async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // Get Authorization header
        let auth_header = parts
            .headers
            .get("Authorization")
            .and_then(|v| v.to_str().ok())
            .ok_or((StatusCode::UNAUTHORIZED, "Missing Authorization header"))?;

        // Extract Bearer token
        let token = auth_header
            .strip_prefix("Bearer ")
            .ok_or((StatusCode::UNAUTHORIZED, "Invalid Authorization format"))?;

        // Get JWT config from extensions (set by middleware)
        let jwt_config = parts
            .extensions
            .get::<Arc<JwtConfig>>()
            .ok_or((StatusCode::INTERNAL_SERVER_ERROR, "Auth not configured"))?;

        // Get token blacklist from extensions (set by middleware)
        let blacklist = parts
            .extensions
            .get::<Arc<TokenBlacklist>>()
            .ok_or((StatusCode::INTERNAL_SERVER_ERROR, "Auth not configured"))?;

        // Check if token is revoked
        if blacklist.is_revoked(token).await {
            return Err((StatusCode::UNAUTHORIZED, "Token has been revoked"));
        }

        // Validate token
        let claims = jwt_config
            .validate_token(token)
            .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid or expired token"))?;

        Ok(AuthenticatedUser::from(claims))
    }
}

/// Optional authenticated user (doesn't reject if not authenticated)
#[derive(Debug, Clone)]
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub struct OptionalUser(pub Option<AuthenticatedUser>);

#[axum::async_trait]
impl<S> FromRequestParts<S> for OptionalUser
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        match AuthenticatedUser::from_request_parts(parts, state).await {
            Ok(user) => Ok(OptionalUser(Some(user))),
            Err(_) => Ok(OptionalUser(None)),
        }
    }
}

/// Require admin role
#[derive(Debug, Clone)]
pub struct AdminUser(pub AuthenticatedUser);

#[axum::async_trait]
impl<S> FromRequestParts<S> for AdminUser
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        let user = AuthenticatedUser::from_request_parts(parts, state).await?;
        if user.is_admin() {
            Ok(AdminUser(user))
        } else {
            Err((StatusCode::FORBIDDEN, "Admin access required"))
        }
    }
}
