pub mod api_tokens;
pub mod jwt;
pub mod passkeys;
pub mod password;

use axum::{async_trait, extract::FromRequestParts, http::request::Parts};
use axum_extra::headers::{authorization::Bearer, Authorization};
use axum_extra::TypedHeader;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

use crate::{
    error::AppError,
    state::{AppState, PgPooledConnection},
};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticatedUser {
    pub user_id: uuid::Uuid,
    pub username: String,
    pub tenant_id: uuid::Uuid,
}

#[async_trait]
impl FromRequestParts<AppState> for AuthenticatedUser {
    type Rejection = AppError;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &AppState,
    ) -> Result<Self, Self::Rejection> {
        if let Some(user) = parts.extensions.get::<AuthenticatedUser>() {
            return Ok(user.clone());
        }

        let TypedHeader(Authorization(bearer)) =
            TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, state)
                .await
                .map_err(|_| AppError::unauthorized())?;

        let claims = state
            .jwt
            .verify_token(bearer.token())
            .map_err(|_| AppError::unauthorized())?;

        let user = AuthenticatedUser {
            user_id: claims.sub,
            username: claims.username,
            tenant_id: claims.tenant_id,
        };

        parts.extensions.insert(user.clone());

        Ok(user)
    }
}

pub struct TenantScopedConn {
    pub conn: PgPooledConnection,
    pub tenant_id: Uuid,
    pub user_id: Uuid,
    pub user: AuthenticatedUser,
}

impl TenantScopedConn {
    pub fn conn(&mut self) -> &mut PgPooledConnection {
        &mut self.conn
    }
}

#[async_trait]
impl FromRequestParts<AppState> for TenantScopedConn {
    type Rejection = AppError;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &AppState,
    ) -> Result<Self, Self::Rejection> {
        let user = AuthenticatedUser::from_request_parts(parts, state).await?;
        let tenant_id = user.tenant_id;
        let conn = state.db_for_tenant(tenant_id)?;

        Ok(Self {
            conn,
            tenant_id,
            user_id: user.user_id,
            user,
        })
    }
}
