/*
 * asklyphe-common nats/authservice/mod.rs
 * - module file + general structs for the asklyphe authentication service
 *
 * Copyright (C) 2025 Real Microsoft, LLC
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

pub mod profile;
pub mod admin;
pub mod announcements;

use std::fmt::{Display, Formatter, Write};
use serde::{Deserialize, Serialize};
use crate::nats::authservice::admin::{AdminCreateAnnouncementRequest, AdminCreateAnnouncementResponse, AdminInviteCodeGenerateRequest, AdminInviteCodeGenerateResponse, AdminListAllUsersRequest, AdminListAllUsersResponse, AdminListInviteCodesRequest, AdminListInviteCodesResponse, AdminUpdateAnnouncementRequest, AdminUpdateAnnouncementResponse, StatsRequest, StatsResponse};
use crate::nats::authservice::announcements::{AnnouncementFullRequest, AnnouncementFullResponse, AnnouncementListRequest, AnnouncementListResponse, LatestAnnouncementRequest, LatestAnnouncementResponse};
use crate::nats::authservice::profile::{ThemeChangeRequest, ThemeChangeResponse, UserInfoRequest, UserInfoResponse};

pub const AUTH_SERVICE: &str = "authsrvc";
pub const AUTH_REPLY_SERVICE: &str = "authsrvcreply";

/// used internally to identify a session
pub type UserPrivateToken = String;

/// used as a semi-secure way to let the user identify individual sessions
pub type UserDisplayToken = String;
/// internal identifier for accounts, should be unique no matter what as this is what we will pass
/// to trusted services to query account info
pub type AccountId = String;
/// internal identifier for an email verification request, given to an account upon creation or email change
/// should be kept private as it will be in the query string of the url that will automatically verify
/// the new email
pub type EmailVerificationId = String;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthServiceQuery {
    pub request: AuthServiceRequest,
    /// if using comms::query_service, you don't need to set this
    pub replyto: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuthServiceRequest {
    RegisterRequest(RegisterRequest),
    VerifyEmailRequest(VerifyEmailRequest),
    LoginRequest(LoginRequest),
    AuthRequest(AuthRequest),
    LogoutRequest(LogoutRequest),
    UserInfoRequest(UserInfoRequest),
    EmailChangeRequest(EmailChangeRequest),
    CancelEmailChangeRequest(CancelEmailChangeRequest),
    ThemeChangeRequest(ThemeChangeRequest),
    StatsRequest(StatsRequest),
    AdminInviteCodeGenerateRequest(AdminInviteCodeGenerateRequest),
    LatestAnnouncementRequest(LatestAnnouncementRequest),
    AnnouncementListRequest(AnnouncementListRequest),
    AnnouncementFullRequest(AnnouncementFullRequest),
    AdminCreateAnnouncementRequest(AdminCreateAnnouncementRequest),
    AdminUpdateAnnouncementRequest(AdminUpdateAnnouncementRequest),
    AdminListInviteCodesRequest(AdminListInviteCodesRequest),
    AdminListAllUsersRequest(AdminListAllUsersRequest),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuthServiceResponse {
    InvalidRequest,
    OtherError(String),
    RegisterResponse(RegisterResponse),
    VerifyEmailResponse(VerifyEmailResponse),
    LoginResponse(LoginResponse),
    AuthResponse(AuthResponse),
    LogoutResponse(LogoutResponse),
    UserInfoResponse(UserInfoResponse),
    EmailChangeResponse(EmailChangeResponse),
    CancelEmailChangeResponse(CancelEmailChangeResponse),
    ThemeChangeResponse(ThemeChangeResponse),
    StatsResponse(StatsResponse),
    AdminInviteCodeGenerateResponse(AdminInviteCodeGenerateResponse),
    LatestAnnouncementResponse(LatestAnnouncementResponse),
    AnnouncementListResponse(AnnouncementListResponse),
    AnnouncementFullResponse(AnnouncementFullResponse),
    AdminCreateAnnouncementResponse(AdminCreateAnnouncementResponse),
    AdminUpdateAnnouncementResponse(AdminUpdateAnnouncementResponse),
    AdminListInviteCodesResponse(AdminListInviteCodesResponse),
    AdminListAllUsersResponse(AdminListAllUsersResponse),
}

// fixme: this should really be done with a macro
impl Display for AuthServiceResponse {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            AuthServiceResponse::InvalidRequest => {
                f.write_str("invalid request")
            }
            AuthServiceResponse::OtherError(_) => {
                f.write_str("other error")
            }
            AuthServiceResponse::RegisterResponse(_) => {
                f.write_str("register response")
            }
            AuthServiceResponse::VerifyEmailResponse(_) => {
                f.write_str("verify email response")
            }
            AuthServiceResponse::LoginResponse(_) => {
                f.write_str("login response")
            }
            AuthServiceResponse::AuthResponse(_) => {
                f.write_str("auth response")
            }
            AuthServiceResponse::LogoutResponse(_) => {
                f.write_str("logout response")
            }
            AuthServiceResponse::UserInfoResponse(_) => {
                f.write_str("user info response")
            }
            AuthServiceResponse::EmailChangeResponse(_) => {
                f.write_str("email change response")
            }
            AuthServiceResponse::CancelEmailChangeResponse(_) => {
                f.write_str("cancel email change response")
            }
            AuthServiceResponse::ThemeChangeResponse(_) => {
                f.write_str("theme change resposne")
            }
            AuthServiceResponse::StatsResponse(_) => {
                f.write_str("stats response")
            }
            AuthServiceResponse::AdminInviteCodeGenerateResponse(_) => {
                f.write_str("admin invite code generate response")
            }
            AuthServiceResponse::LatestAnnouncementResponse(_) => {
                f.write_str("latest announcement response")
            }
            AuthServiceResponse::AnnouncementListResponse(_) => {
                f.write_str("announcement list response")
            }
            AuthServiceResponse::AnnouncementFullResponse(_) => {
                f.write_str("announcement full response")
            }
            AuthServiceResponse::AdminCreateAnnouncementResponse(_) => {
                f.write_str("admin create announcement response")
            }
            AuthServiceResponse::AdminUpdateAnnouncementResponse(_) => {
                f.write_str("admin update announcement response")
            }
            AuthServiceResponse::AdminListInviteCodesResponse(_) => {
                f.write_str("admin list invite codes response")
            }
            AuthServiceResponse::AdminListAllUsersResponse(_) => {
                f.write_str("admin list all users response")
            }
        }
    }
}

/// # RegisterRequest
/// sent from frontend -> authsrvc to attempt to register a user account
/// expecting a AuthServiceResponse::RegisterResponse back
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisterRequest {
    /// username may be eventually used in settings where users will interact; preventing email addresses being shared
    pub username: String,
    pub email: String,
    /// value is NOT hashed over nats! make sure that tls is enabled!
    pub password: String,
    /// during private beta, only accept requests with a valid invite code;
    /// afterwards, an empty invite code will indicate a free trial
    pub invite_code: String,
}

/// # RegisterResponse
/// sent from authsrvc -> frontend once the RegisterRequest was completed
/// success should give user a message to check their email for verification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RegisterResponse {
    Success,
    Failure(RegisterError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RegisterError {
    /// probably best to not display the actual error to the user, for security reasons
    InternalServer(String),
    Password(PasswordError),
    Email(EmailError),
    Username(UsernameError),
    BadInviteCode,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PasswordError {
    /// weird / non-ascii characters in password
    InvalidCharacters,
    /// password too short
    TooShort,
    /// commonly used
    TooCommon,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EmailError {
    /// doesn't match email format
    InvalidEmail,
    /// email already in use
    EmailTaken,
    /// email blacklisted (e.g. if email server is commonly used for spam)
    EmailBlacklisted,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum UsernameError {
    /// contains invalid characters (i.e. not a-z, 0-9, -, _, ~, maybe some others?)
    /// usernames should be case insensitive
    InvalidCharacters,
    /// usernames should probably be at minimum 3 chars? idk i'm just coming up with a random value
    TooShort,
    /// no duplicate usernames
    Taken,
    /// try to avoid people using slurs as usernames
    Inappropriate,
}

/// # VerifyEmailRequest
/// sent from frontend -> authsrvc to attempt to verify an email
/// expecting a AuthServiceResponse::VerifyEmailResponse back
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyEmailRequest {
    pub username: String,
    pub token: EmailVerificationId,
}

/// # VerifyEmailResponse
/// sent from authsrvc -> frontend once the VerifyEmailRequest completes
/// if successful, it's assumed that a user can now log in to their account without issue
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VerifyEmailResponse {
    Success,
    InvalidToken,
    ExpiredToken,
    InternalServerError(String),
}

/// # LoginRequest
/// sent from frontend -> authsrvc to attempt to login to a user account
/// expecting a AuthServiceResponse::LoginResponse back
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginRequest {
    pub email: String,
    /// value is NOT hashed over nats! make sure that tls is enabled!
    pub password: String,
}

/// # LoginResponse
/// sent from authsrvc -> frontend once the LoginRequest was completed
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LoginResponse {
    Success(TokenAssignment),
    Failure(LoginError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LoginError {
    /// probably best to not display the actual error to the user, for security reasons
    InternalServer(String),
    /// to avoid bruteforce attempts on accounts, don't say what part of the given email/pass
    /// combo was invalid
    InvalidAccount,
    /// best to include a message on the frontend indicating that the user check their email for more info
    SuspendedAccount,
    /// don't let people login unless they've verified their email
    AccountNotVerified,
}

/// # AuthRequest
/// sent from any service -> authsrvc to check credentials on an internal request
/// expecting a AuthServiceResponse::AuthResponse back
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthRequest {
    pub token: UserPrivateToken,
}

/// # AuthResponse
/// sent from authsrvc -> requesting service once the AuthRequest was completed
/// errors should ideally be forwarded to the user if necessary
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuthResponse {
    Success(AccountId),
    Failure(AuthError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuthError {
    /// probably best to not display the actual error to the user, for security reasons
    InternalServer(String),
    /// if the token is expired
    TokenExpired,
    /// if the account was suspended between now and the last valid request
    AccountSuspended,
    /// if the token just doesn't exist
    TokenInvalid,
}

/// # TokenAssignment
/// generic struct used in usersrvc -> frontend messages when the user needs to have their session assigned
/// (such as after logging in)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenAssignment {
    pub token: UserPrivateToken,
    /// unix utc timestamp of death date
    pub ttl: i64,
}

/// # LogoutRequest
/// sent from frontend -> authsrvc to delete a session
/// expecting a AuthServiceResponse::LogoutResponse back
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogoutRequest {
    pub token: UserPrivateToken,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LogoutResponse {
    Success,
    InternalServerError(String),
    /// not that important what happened with the token, the frontend is already discarding it regardless
    BadTokenError,
}

/// # EmailChangeRequest
/// sent from frontend -> authsrvc to start the process of changing a user's associated email address
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailChangeRequest {
    pub token: UserPrivateToken,
    pub new_email: String,
}

/// # EmailChangeResponse
/// authsrvc -> frontend, response to EmailChangeRequest
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EmailChangeResponse {
    /// verification email has been sent
    Success,
    /// user token invalid, log out
    InvalidToken,
    /// new email was not valid
    InvalidEmail,
    /// internal error
    InternalError,
}

/// # CancelEmailChangeRequest
/// frontend -> authsrvc, cancel an in-progress email change
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelEmailChangeRequest {
    pub token: UserPrivateToken,
}

/// # CancelEmailChangeResponse
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CancelEmailChangeResponse {
    Success,
    /// user token invalid, log out
    InvalidToken,
    /// internal error
    InternalError,
}