/*
 * authservice process/basic_accounts.rs
 * - basic authentication + creation for accounts, as well as a few other things
 *
 * 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/>.
*/

use std::future::Future;
use asklyphe_common::nats::authservice::{AuthError, AuthRequest, AuthResponse, AuthServiceResponse, CancelEmailChangeRequest, CancelEmailChangeResponse, EmailChangeRequest, EmailChangeResponse, EmailError, LoginError, LoginRequest, LoginResponse, LogoutRequest, LogoutResponse, PasswordError, RegisterError, RegisterRequest, RegisterResponse, TokenAssignment, UsernameError, VerifyEmailRequest, VerifyEmailResponse};
use chrono::{DateTime, Utc};
use log::{debug, error};
use rand::Rng;
use sea_orm::DatabaseConnection;
use crate::db::{invite_code, session, user};
use crate::db::invite_code::ConsumeInviteCodeError;
use crate::db::session::{CreateSessionError, DeleteSessionError, FetchSessionError};
use crate::db::user::{CreateUserError, DeleteUserError, EmailChangeError, FetchUserError, RegisterVerificationCodeError, VerifyEmailPassComboError, VerifyVerificationCodeError};
use crate::{email, AUTH_URL};

fn generate_email_verification_code() -> String {
    rand::thread_rng()
        .sample_iter(&rand::distributions::Alphanumeric)
        .take(16)
        .map(char::from)
        .collect()
}

pub async fn register_request(db: &DatabaseConnection, request: RegisterRequest) -> RegisterResponse {
    // goes something like:
    // 1. create user in db
    // 2. try to consume invite code
    // 2.5. if that fails, delete user from db
    // 3. otherwise, register email verification code in db
    // 4. this is where we'd send emails out with the verification code, if we had that set up...
    let user = user::create_user(
        db,
        request.username.clone(),
        request.username.clone(),
        request.email.clone(),
        request.password).await;
    if let Err(e) = user {
        return match e {
            CreateUserError::UsernameTaken => {
                RegisterResponse::Failure(RegisterError::Username(UsernameError::Taken))
            }
            CreateUserError::EmailTaken => {
                RegisterResponse::Failure(RegisterError::Email(EmailError::EmailTaken))
            }
            CreateUserError::UsernameError => {
                RegisterResponse::Failure(RegisterError::Username(UsernameError::InvalidCharacters))
            }
            CreateUserError::DisplayNameError => {
                unimplemented!("we don't explicitly allow display names to be set, username error would be hit before display name error")
            }
            CreateUserError::EmailError => {
                RegisterResponse::Failure(RegisterError::Email(EmailError::InvalidEmail))
            }
            CreateUserError::PasswordError => {
                // todo: actual password errors that tell you why it's bad
                RegisterResponse::Failure(RegisterError::Password(PasswordError::TooShort))
            }
            CreateUserError::DatabaseError => {
                RegisterResponse::Failure(RegisterError::InternalServer("database error".to_string()))
            }
        };
    }
    let user_id = user.unwrap();
    if let Err(e) = invite_code::consume_invite_code(db, request.invite_code.clone(), user_id.clone()).await {
        if let Err(e) = user::delete_user(db, user_id.clone()).await {
            return match e {
                DeleteUserError::UserDoesntExist => {
                    RegisterResponse::Failure(RegisterError::InternalServer("unexpected missing user".to_string()))
                }
                DeleteUserError::DatabaseError => {
                    RegisterResponse::Failure(RegisterError::InternalServer("database error".to_string()))
                }
            };
        }
        return match e {
            ConsumeInviteCodeError::InvalidUser => {
                RegisterResponse::Failure(RegisterError::InternalServer("unexpected missing user".to_string()))
            }
            ConsumeInviteCodeError::DatabaseError => {
                RegisterResponse::Failure(RegisterError::InternalServer("database error".to_string()))
            }
            _ => {
                RegisterResponse::Failure(RegisterError::BadInviteCode)
            }
        };
    }

    let verification_code = generate_email_verification_code();
    if let Err(e) = user::register_verification_code(db, user_id.clone(), verification_code.clone()).await {
        if let Err(e) = user::delete_user(db, user_id.clone()).await {
            match e {
                DeleteUserError::UserDoesntExist => {
                    error!("failed to register verify code during signup, and couldn't delete the user because they don't exist?")
                }
                DeleteUserError::DatabaseError => {
                    error!("database error while trying to delete user after registering verify code failed during signup")
                }
            }
        }
        return match e {
            RegisterVerificationCodeError::UserIsVerifiedAndNotChangingTheirEmail => {
                RegisterResponse::Failure(RegisterError::InternalServer("unexpected corrupt user struct".to_string()))
            }
            RegisterVerificationCodeError::UserDoesntExist => {
                RegisterResponse::Failure(RegisterError::InternalServer("unexpected missing user".to_string()))
            }
            RegisterVerificationCodeError::DatabaseError => {
                RegisterResponse::Failure(RegisterError::InternalServer("database error".to_string()))
            }
        };
    }

    debug!("verification code for {} is \"{}\"", request.username, verification_code);
    email::send_verification_code_email(&request.email, &request.username, format!("{}/verify?username={}&token={}", AUTH_URL.as_str(), request.username, verification_code).as_str());

    RegisterResponse::Success
}

pub async fn verify_email(db: &DatabaseConnection, request: VerifyEmailRequest) -> VerifyEmailResponse {
    let user_id = user::username_to_userid(db, request.username).await;
    if let Err(e) = user_id {
        return match e {
            FetchUserError::UserNotFound => {
                VerifyEmailResponse::InvalidToken
            }
            FetchUserError::DatabaseError => {
                VerifyEmailResponse::InternalServerError("database error during username2userid".to_string())
            }
        };
    }
    let user_id = user_id.unwrap();

    if let Err(e) = user::verify_verification_code(db, user_id, request.token).await {
        return match e {
            VerifyVerificationCodeError::UserAlreadyVerified => {
                VerifyEmailResponse::InvalidToken
            }
            VerifyVerificationCodeError::VerificationCodeInvalid => {
                VerifyEmailResponse::InvalidToken
            }
            VerifyVerificationCodeError::UserDoesntExist => {
                VerifyEmailResponse::InvalidToken
            }
            VerifyVerificationCodeError::DatabaseError => {
                VerifyEmailResponse::InternalServerError("database error during username2userid".to_string())
            }
        };
    }

    VerifyEmailResponse::Success
}

pub async fn login(db: &DatabaseConnection, request: LoginRequest) -> LoginResponse {
    // 1. verify that credentials are correct
    // 2. create session
    // 3. return token
    if let Err(e) = user::verify_email_pass_combo(db, request.email.clone(), request.password).await {
        return match e {
            VerifyEmailPassComboError::ComboInvalid => {
                LoginResponse::Failure(LoginError::InvalidAccount)
            }
            VerifyEmailPassComboError::UserDoesntExist => {
                LoginResponse::Failure(LoginError::InvalidAccount)
            }
            VerifyEmailPassComboError::DatabaseError => {
                LoginResponse::Failure(LoginError::InternalServer("database error while verifying email/pass combo".to_string()))
            }
        };
    }
    let user_id = match user::email_to_userid(db, request.email).await {
        Ok(user_id) => { user_id }
        Err(e) => {
            return match e {
                FetchUserError::UserNotFound => {
                    LoginResponse::Failure(LoginError::InvalidAccount)
                }
                FetchUserError::DatabaseError => {
                    LoginResponse::Failure(LoginError::InternalServer("database error while converting email to userid".to_string()))
                }
            };
        }
    };

    match user::check_if_verified(db, user_id.clone()).await {
        Ok(b) => {
            if !b {
                return LoginResponse::Failure(LoginError::AccountNotVerified);
            }
        }
        Err(e) => {
            return match e {
                FetchUserError::UserNotFound => {
                    LoginResponse::Failure(LoginError::InvalidAccount)
                }
                FetchUserError::DatabaseError => {
                    LoginResponse::Failure(LoginError::InternalServer("database error while checking verification status".to_string()))
                }
            };
        }
    }

    let session_token = match session::create_session(db, user_id, "".to_string()).await {
        Ok(s) => s,
        Err(e) => {
            return match e {
                CreateSessionError::UserDoesntExist => {
                    LoginResponse::Failure(LoginError::InvalidAccount)
                }
                CreateSessionError::DatabaseError => {
                    LoginResponse::Failure(LoginError::InternalServer("database error while creating session".to_string()))
                }
            };
        }
    };

    let death_date = match session::get_session_death_date(db, session_token.clone()).await {
        Ok(s) => s,
        Err(e) => {
            return match e {
                FetchSessionError::SessionInvalid => {
                    error!("unexpected session invalid after just creating it during login!");
                    LoginResponse::Failure(LoginError::InternalServer("database error while creating session".to_string()))
                }
                FetchSessionError::SessionDoesntExist => {
                    error!("unexpected session not found after just creating it during login!");
                    LoginResponse::Failure(LoginError::InternalServer("database error while creating session".to_string()))
                }
                FetchSessionError::DatabaseError => {
                    LoginResponse::Failure(LoginError::InternalServer("database error while creating session".to_string()))
                }
            };
        }
    };

    LoginResponse::Success(TokenAssignment {
        token: session_token,
        ttl: death_date.timestamp(),
    })
}

pub async fn verify_session(db: &DatabaseConnection, request: AuthRequest) -> AuthResponse {
    let uid = match session::get_userid_from_session(db, request.token, true).await {
        Ok(u) => u,
        Err(e) => {
            return match e {
                FetchSessionError::SessionInvalid => {
                    AuthResponse::Failure(AuthError::TokenExpired)
                }
                FetchSessionError::SessionDoesntExist => {
                    AuthResponse::Failure(AuthError::TokenInvalid)
                }
                FetchSessionError::DatabaseError => {
                    AuthResponse::Failure(AuthError::InternalServer("database error while querying user id".to_string()))
                }
            };
        }
    };

    AuthResponse::Success(uid)
}

pub async fn logout(db: &DatabaseConnection, request: LogoutRequest) -> LogoutResponse {
    match session::delete_session(db, request.token).await {
        Ok(_) => LogoutResponse::Success,
        Err(e) => match e {
            DeleteSessionError::SessionDoesntExist => { LogoutResponse::BadTokenError }
            DeleteSessionError::DatabaseError => { LogoutResponse::InternalServerError("database error while deleting session".to_string()) }
        }
    }
}

pub async fn email_change(db: &DatabaseConnection, request: EmailChangeRequest) -> EmailChangeResponse {
    let uid = match session::get_userid_from_session(db, request.token, true).await {
        Ok(u) => u,
        Err(e) => {
            return match e {
                FetchSessionError::SessionInvalid => {
                    EmailChangeResponse::InvalidToken
                }
                FetchSessionError::SessionDoesntExist => {
                    EmailChangeResponse::InvalidToken
                }
                FetchSessionError::DatabaseError => {
                    EmailChangeResponse::InternalError
                }
            };
        }
    };

    let verification_code = generate_email_verification_code();
    let res = user::start_email_change(db, uid.clone(), request.new_email, verification_code.clone()).await;
    if let Err(e) = res {
        return match e {
            EmailChangeError::UserNotFound => {
                EmailChangeResponse::InvalidToken
            }
            EmailChangeError::NewEmailIsInvalid => {
                EmailChangeResponse::InvalidEmail
            }
            EmailChangeError::DatabaseError => {
                EmailChangeResponse::InternalError
            }
        };
    }
    debug!("verification code for new email on {} is \"{}\"", {user::user_info(db, uid.clone()).await.map(|v| v.username).unwrap_or(uid.clone())}, verification_code);
    EmailChangeResponse::Success
}

pub async fn cancel_email_change(db: &DatabaseConnection, request: CancelEmailChangeRequest) -> CancelEmailChangeResponse {
    let uid = match session::get_userid_from_session(db, request.token, true).await {
        Ok(u) => u,
        Err(e) => {
            return match e {
                FetchSessionError::SessionInvalid => {
                    CancelEmailChangeResponse::InvalidToken
                }
                FetchSessionError::SessionDoesntExist => {
                    CancelEmailChangeResponse::InvalidToken
                }
                FetchSessionError::DatabaseError => {
                    CancelEmailChangeResponse::InternalError
                }
            };
        }
    };
    
    if let Err(e) = user::cancel_email_change(db, uid).await {
        return match e {
            FetchUserError::UserNotFound => {
                CancelEmailChangeResponse::InvalidToken
            }
            FetchUserError::DatabaseError => {
                CancelEmailChangeResponse::InternalError
            }
        };
    }
    
    CancelEmailChangeResponse::Success
}
