/*
 * authservice process/admin.rs
 * - admin request functions
 *
 * 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::admin::{AdminCreateAnnouncementRequest, AdminCreateAnnouncementResponse, AdminInviteCodeGenerateRequest, AdminInviteCodeGenerateResponse, AdminListAllUsersRequest, AdminListAllUsersResponse, AdminListInviteCodesRequest, AdminListInviteCodesResponse, AdminUpdateAnnouncementRequest, AdminUpdateAnnouncementResponse, InviteCodeSerialized, Stats, StatsRequest, StatsResponse, UserSerialized};
use log::error;
use sea_orm::DatabaseConnection;
use entity::invite_code::Model;
use crate::db::{announcements, invite_code, session, user};
use crate::db::announcements::{AnnouncementInsertError, AnnouncementUpdateError};
use crate::db::invite_code::{InviteCodeGenerationError, InviteCodeListError};
use crate::db::session::FetchSessionError;
use crate::db::user::{FetchUserError, UserInfo};
use crate::email::send_announcement_email;

async fn admin_info(db: &DatabaseConnection, token: String) -> Result<Option<UserInfo>, FetchSessionError> {
    let uid = match session::get_userid_from_session(db, token, true).await {
        Ok(u) => u,
        Err(e) => {
            return match e {
                FetchSessionError::SessionInvalid => {
                    Err(FetchSessionError::SessionInvalid)
                }
                FetchSessionError::SessionDoesntExist => {
                    Err(FetchSessionError::SessionDoesntExist)
                }
                FetchSessionError::DatabaseError => {
                    Err(FetchSessionError::DatabaseError)
                }
            }
        }
    };
    let info = match user::user_info(db, uid).await {
        Ok(u) => u,
        Err(e) => return match e {
            FetchUserError::UserNotFound => {
                Err(FetchSessionError::SessionDoesntExist)
            }
            FetchUserError::DatabaseError => {
                Err(FetchSessionError::DatabaseError)
            }
        }
    };
    if !info.admin {
        return Ok(None);
    }
    Ok(Some(info))
}

pub async fn stats(db: &DatabaseConnection, request: StatsRequest) -> StatsResponse {
    let info = admin_info(db, request.token).await;
    let _info = if let Ok(info) = info {
        if let Some(info) = info {
            info
        } else {
            return StatsResponse::AccessDenied;
        }
    } else {
        return match info.unwrap_err() {
            FetchSessionError::SessionInvalid => {
                StatsResponse::Logout
            }
            FetchSessionError::SessionDoesntExist => {
                StatsResponse::Logout
            }
            FetchSessionError::DatabaseError => {
                StatsResponse::InternalServerError("database error".to_string())
            }
        };
    };

    let last_ten_users = match user::last_ten_usernames(db).await {
        Ok(u) => u,
        Err(e) => return match e {
            FetchUserError::UserNotFound => { unreachable!() }
            FetchUserError::DatabaseError => { StatsResponse::InternalServerError("database error".to_string() )}
        }
    };
    let user_count = match user::user_count(db).await {
        Ok(u) => u,
        Err(e) => return match e {
            FetchUserError::UserNotFound => { unreachable!() }
            FetchUserError::DatabaseError => { StatsResponse::InternalServerError("database error".to_string() )}
        }
    };
    let admin_count = match user::admin_count(db).await {
        Ok(u) => u,
        Err(e) => return match e {
            FetchUserError::UserNotFound => { unreachable!() }
            FetchUserError::DatabaseError => { StatsResponse::InternalServerError("database error".to_string() )}
        }
    };
    let active_verification_requests = match user::unverified_count(db).await {
        Ok(u) => u,
        Err(e) => return match e {
            FetchUserError::UserNotFound => { unreachable!() }
            FetchUserError::DatabaseError => { StatsResponse::InternalServerError("database error".to_string() )}
        }
    };
    
    StatsResponse::Success(Stats {
        last_ten_users,
        user_count,
        admin_count,
        active_verification_requests,
    })
}

pub async fn admin_invite_code_gen(db: &DatabaseConnection, request: AdminInviteCodeGenerateRequest) -> AdminInviteCodeGenerateResponse {
    let info = admin_info(db, request.token).await;
    let info = if let Ok(info) = info {
        if let Some(info) = info {
            info
        } else {
            return AdminInviteCodeGenerateResponse::AccessDenied;
        }
    } else {
        return match info.unwrap_err() {
            FetchSessionError::SessionInvalid => {
                AdminInviteCodeGenerateResponse::Logout
            }
            FetchSessionError::SessionDoesntExist => {
                AdminInviteCodeGenerateResponse::Logout
            }
            FetchSessionError::DatabaseError => {
                AdminInviteCodeGenerateResponse::InternalServerError("database error".to_string())
            }
        };
    };
    
    let issuer = info.username;
    
    match invite_code::insert_new_invitecode(db, request.invite_code, issuer.clone(), issuer, request.comment).await {
        Ok(_) => {
            AdminInviteCodeGenerateResponse::Success
        }
        Err(e) => match e {
            InviteCodeGenerationError::CodeAlreadyInserted => {
                AdminInviteCodeGenerateResponse::CodeAlreadyExists
            }
            InviteCodeGenerationError::DatabaseError => {
                AdminInviteCodeGenerateResponse::InternalServerError("database error".to_string())
            }
        }
    }
}

pub async fn admin_create_announcement(db: &DatabaseConnection, request: AdminCreateAnnouncementRequest) -> AdminCreateAnnouncementResponse {
    let info = admin_info(db, request.token).await;
    let info = if let Ok(info) = info {
        if let Some(info) = info {
            info
        } else {
            return AdminCreateAnnouncementResponse::AccessDenied;
        }
    } else {
        return match info.unwrap_err() {
            FetchSessionError::SessionInvalid => {
                AdminCreateAnnouncementResponse::Logout
            }
            FetchSessionError::SessionDoesntExist => {
                AdminCreateAnnouncementResponse::Logout
            }
            FetchSessionError::DatabaseError => {
                AdminCreateAnnouncementResponse::InternalServerError("database error".to_string())
            }
        };
    };

    let uid = match user::username_to_userid(db, info.username.clone()).await {
        Ok(v) => v,
        Err(e) => {
            match e {
                FetchUserError::UserNotFound => {
                    error!("user id not found for username {}", info.username);
                }
                FetchUserError::DatabaseError => {
                }
            }
            return AdminCreateAnnouncementResponse::InternalServerError("database error".to_string());
        }
    };

    match announcements::create_announcement(db, request.slug, request.title.clone(), request.short_text, request.full_markdown_text.clone(), uid).await {
        Ok(_) => {
            if request.send_emails {
                let email_list = user::email_list(db).await.unwrap_or_else(|e| match e {
                    FetchUserError::UserNotFound => {
                        unreachable!()
                    }
                    FetchUserError::DatabaseError => {
                        error!("couldn't send announcement emails due to a database error! you will need to send them manually!");
                        vec![]
                    }
                });
                for (username, email) in email_list {
                    send_announcement_email(&email, &username, &request.title, &request.full_markdown_text);
                }
            }
            AdminCreateAnnouncementResponse::Success
        }
        Err(e) => match e {
            AnnouncementInsertError::SlugInUse => {
                AdminCreateAnnouncementResponse::SlugTaken
            }
            AnnouncementInsertError::DatabaseError => {
                AdminCreateAnnouncementResponse::InternalServerError("database error".to_string())
            }
        }
    }
}

pub async fn admin_update_announcement(db: &DatabaseConnection, request: AdminUpdateAnnouncementRequest) -> AdminUpdateAnnouncementResponse {
    let info = admin_info(db, request.token).await;
    let _info = if let Ok(info) = info {
        if let Some(info) = info {
            info
        } else {
            return AdminUpdateAnnouncementResponse::AccessDenied;
        }
    } else {
        return match info.unwrap_err() {
            FetchSessionError::SessionInvalid => {
                AdminUpdateAnnouncementResponse::Logout
            }
            FetchSessionError::SessionDoesntExist => {
                AdminUpdateAnnouncementResponse::Logout
            }
            FetchSessionError::DatabaseError => {
                AdminUpdateAnnouncementResponse::InternalServerError("database error".to_string())
            }
        };
    };

    match announcements::update_announcement(db, request.slug, request.new_title, request.new_short_text, request.new_full_markdown_text).await {
        Ok(_) => {
            AdminUpdateAnnouncementResponse::Success
        }
        Err(e) => match e {
            AnnouncementUpdateError::SlugNotFound => {
                AdminUpdateAnnouncementResponse::OriginalNotFound
            }
            AnnouncementUpdateError::DatabaseError => {
                AdminUpdateAnnouncementResponse::InternalServerError("database error".to_string())
            }
        }
    }
}

pub async fn admin_list_invite_codes(db: &DatabaseConnection, request: AdminListInviteCodesRequest) -> AdminListInviteCodesResponse {
    let info = admin_info(db, request.token).await;
    let _info = if let Ok(info) = info {
        if let Some(info) = info {
            info
        } else {
            return AdminListInviteCodesResponse::AccessDenied;
        }
    } else {
        return match info.unwrap_err() {
            FetchSessionError::SessionInvalid => {
                AdminListInviteCodesResponse::Logout
            }
            FetchSessionError::SessionDoesntExist => {
                AdminListInviteCodesResponse::Logout
            }
            FetchSessionError::DatabaseError => {
                AdminListInviteCodesResponse::InternalServerError("database error".to_string())
            }
        };
    };

    match invite_code::list_invite_codes(db, request.show_used).await {
        Ok(v) => {
            let mut serialized = vec![];
            for v in v {
                let used_by = if let Some(used_by) = v.used_by {
                    Some(match user::user_info(db, used_by.clone()).await {
                        Ok(v) => { v.username }
                        Err(v) => { format!("error fetching {}, err is {:?}", used_by, v) }
                    })
                } else {
                    None
                };
                serialized.push(
                InviteCodeSerialized {
                    code: v.code,
                    comment: v.comment.unwrap_or_default(),
                    creator: v.issuer.unwrap_or_default(),
                    root_creator: v.root_issuer,
                    created_at: v.created_on.and_utc().to_string(),
                    used_at: v.used_on.map(|v| v.and_utc().to_string()),
                    used_by,
                });
            }
            AdminListInviteCodesResponse::Success(serialized)
        }
        Err(e) => {
            match e {
                InviteCodeListError::DatabaseError => {
                    AdminListInviteCodesResponse::InternalServerError("database error".to_string())
                }
            }
        }
    }
}

pub async fn admin_list_all_users(db: &DatabaseConnection, request: AdminListAllUsersRequest) -> AdminListAllUsersResponse {
    let info = admin_info(db, request.token).await;
    let _info = if let Ok(info) = info {
        if let Some(info) = info {
            info
        } else {
            return AdminListAllUsersResponse::AccessDenied;
        }
    } else {
        return match info.unwrap_err() {
            FetchSessionError::SessionInvalid => {
                AdminListAllUsersResponse::Logout
            }
            FetchSessionError::SessionDoesntExist => {
                AdminListAllUsersResponse::Logout
            }
            FetchSessionError::DatabaseError => {
                AdminListAllUsersResponse::InternalServerError("database error".to_string())
            }
        };
    };
    
    match user::all_users(db).await {
        Ok(v) => {
            AdminListAllUsersResponse::Success(v.into_iter().map(|v| {
                UserSerialized {
                    username: v.username,
                    email: v.email,
                    verified: v.verified,
                    admin: v.admin,
                }
            }).collect())
        }
        Err(e) => {
            match e {
                FetchUserError::UserNotFound => {
                    unreachable!("usernotfound should not be returned from user::all_users!")
                }
                FetchUserError::DatabaseError => {
                    AdminListAllUsersResponse::InternalServerError("database error".to_string())
                }
            }
        }
    }
}