mod validation;

pub use validation::{validate_email, validate_password, validate_username};

use serde::{Deserialize, Serialize};

/// Tokens returned by forest-server after login/register.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthTokens {
    pub access_token: String,
    pub refresh_token: String,
    pub expires_in_seconds: i64,
}

/// Minimal user info from forest-server.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub user_id: String,
    pub username: String,
    pub emails: Vec<UserEmail>,
}

/// Public user profile (no emails).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserProfile {
    pub user_id: String,
    pub username: String,
    pub created_at: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserEmail {
    pub email: String,
    pub verified: bool,
}

/// A personal access token (metadata only, no raw key).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersonalAccessToken {
    pub token_id: String,
    pub name: String,
    pub scopes: Vec<String>,
    pub created_at: Option<String>,
    pub last_used: Option<String>,
    pub expires_at: Option<String>,
}

/// Result of creating a PAT - includes the raw key shown once.
#[derive(Debug, Clone)]
pub struct CreatedToken {
    pub token: PersonalAccessToken,
    pub raw_token: String,
}

#[derive(Debug, Clone, thiserror::Error)]
pub enum AuthError {
    #[error("invalid credentials")]
    InvalidCredentials,

    #[error("already exists: {0}")]
    AlreadyExists(String),

    #[error("not authenticated")]
    NotAuthenticated,

    #[error("token expired")]
    TokenExpired,

    #[error("forest-server unavailable: {0}")]
    Unavailable(String),

    #[error("{0}")]
    Other(String),
}

/// Trait for communicating with forest-server's UsersService.
/// Object-safe via async_trait so we can use `Arc<dyn ForestAuth>`.
#[async_trait::async_trait]
pub trait ForestAuth: Send + Sync {
    async fn register(
        &self,
        username: &str,
        email: &str,
        password: &str,
    ) -> Result<AuthTokens, AuthError>;

    async fn login(
        &self,
        identifier: &str,
        password: &str,
    ) -> Result<AuthTokens, AuthError>;

    async fn refresh_token(
        &self,
        refresh_token: &str,
    ) -> Result<AuthTokens, AuthError>;

    async fn logout(&self, refresh_token: &str) -> Result<(), AuthError>;

    async fn get_user(&self, access_token: &str) -> Result<User, AuthError>;

    async fn get_user_by_username(
        &self,
        access_token: &str,
        username: &str,
    ) -> Result<UserProfile, AuthError>;

    async fn list_tokens(
        &self,
        access_token: &str,
        user_id: &str,
    ) -> Result<Vec<PersonalAccessToken>, AuthError>;

    async fn create_token(
        &self,
        access_token: &str,
        user_id: &str,
        name: &str,
    ) -> Result<CreatedToken, AuthError>;

    async fn delete_token(
        &self,
        access_token: &str,
        token_id: &str,
    ) -> Result<(), AuthError>;

    async fn update_username(
        &self,
        access_token: &str,
        user_id: &str,
        new_username: &str,
    ) -> Result<User, AuthError>;

    async fn change_password(
        &self,
        access_token: &str,
        user_id: &str,
        current_password: &str,
        new_password: &str,
    ) -> Result<(), AuthError>;

    async fn add_email(
        &self,
        access_token: &str,
        user_id: &str,
        email: &str,
    ) -> Result<UserEmail, AuthError>;

    async fn remove_email(
        &self,
        access_token: &str,
        user_id: &str,
        email: &str,
    ) -> Result<(), AuthError>;
}
