/*
 * asklyphe-frontend routes/admin.rs
 * - http routes for the admin panel
 *
 * 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::sync::Arc;
use askama::Template;
use asklyphe_common::nats::authservice::{AuthServiceQuery, AuthServiceRequest, AuthServiceResponse};
use asklyphe_common::nats::authservice::admin::{AdminCreateAnnouncementRequest, AdminCreateAnnouncementResponse, AdminInviteCodeGenerateRequest, AdminInviteCodeGenerateResponse, AdminListAllUsersRequest, AdminListAllUsersResponse, AdminListInviteCodesRequest, AdminListInviteCodesResponse, InviteCodeSerialized, Stats, StatsRequest, StatsResponse, UserSerialized};
use asklyphe_common::nats::authservice::profile::{UserInfoRequest, UserInfoResponse};
use asklyphe_common::nats::comms;
use asklyphe_common::nats::comms::ServiceResponse;
use async_nats::jetstream;
use axum::{Extension, Form};
use axum::response::{IntoResponse, Redirect};
use axum_extra::extract::CookieJar;
use rand::Rng;
use serde::Deserialize;
use tracing::{debug, error};
use crate::Opts;
use crate::routes::{authenticate_user, UserInfo};

const MOTD: &[&str] = &[
    "be evil :3c",
    "pong",
    ">:o",
    "\" UNION SELECT username, password FROM users; -- ",
];

#[derive(Template)]
#[template(path = "admin/error.html")]
pub struct AdminErrorTemplate {
    motd: &'static str,

    error: String,
}

#[derive(Template)]
#[template(path = "admin/home.html")]
pub struct AdminHomeTemplate {
    motd: &'static str,

    last_ten_users: Vec<String>,
    user_count: usize,
    admin_count: usize,
    active_verification_requests: usize,
}

fn motd() -> &'static str {
    let mut rng = rand::thread_rng();
    MOTD[rng.gen_range(0..MOTD.len())]
}

fn adminerror(message: String) -> impl IntoResponse {
    AdminErrorTemplate {
        motd: motd(),
        error: message,
    }
}

async fn stats(nats: Arc<jetstream::Context>, token: String) -> Result<Stats, impl IntoResponse> {
    let response = comms::query_service(
        comms::Query::AuthService(AuthServiceQuery {
            request: AuthServiceRequest::StatsRequest(StatsRequest {
                token,
            }),
            replyto: "".to_string(),
        }),
        &nats,
        false,
    ).await;
    if let Err(e) = response {
        error!("nats error: {:?}", e);
        Err(adminerror(format!("nats error: {:?}", e)).into_response())
    } else {
        let response = response.unwrap();
        match response {
            ServiceResponse::SearchService(_) => {
                error!("sent search service response when asking for auth service!! investigate ASAP!!!");
                Err(adminerror("sent search service response when asking auth service!".to_string()).into_response())
            }
            ServiceResponse::BingService(_) => {
                error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
                Err(adminerror("sent bing service response when asking auth service!".to_string()).into_response())
            }
            ServiceResponse::AuthService(r) => match r {
                AuthServiceResponse::StatsResponse(stats) => {
                    match stats {
                        StatsResponse::Success(stats) => {
                            Ok(stats)
                        }
                        StatsResponse::InternalServerError(e) => {
                            Err(adminerror(format!("internal server error: {e}")).into_response())
                        }
                        StatsResponse::Logout => {
                            Err(Redirect::to("/").into_response())
                        }
                        StatsResponse::AccessDenied => {
                            Err(Redirect::to("/").into_response())
                        }
                    }
                }
                x => {
                    error!("auth service gave {} to our user info request!", x);
                    Err(adminerror(format!("auth service gave {} to our user info request!", x)).into_response())
                }
            },
        }
    }
}

async fn list_invite_codes(nats: Arc<jetstream::Context>, token: String, show_used: bool) -> Result<Vec<InviteCodeSerialized>, impl IntoResponse> {
    let response = comms::query_service(
        comms::Query::AuthService(AuthServiceQuery {
            request: AuthServiceRequest::AdminListInviteCodesRequest(AdminListInviteCodesRequest {
                token,
                show_used,
            }),
            replyto: "".to_string(),
        }),
        &nats,
        false,
    ).await;
    if let Err(e) = response {
        error!("nats error: {:?}", e);
        Err(adminerror(format!("nats error: {:?}", e)).into_response())
    } else {
        let response = response.unwrap();
        match response {
            ServiceResponse::SearchService(_) => {
                error!("sent search service response when asking for auth service!! investigate ASAP!!!");
                Err(adminerror("sent search service response when asking auth service!".to_string()).into_response())
            }
            ServiceResponse::BingService(_) => {
                error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
                Err(adminerror("sent bing service response when asking auth service!".to_string()).into_response())
            }
            ServiceResponse::AuthService(r) => match r {
                AuthServiceResponse::AdminListInviteCodesResponse(v) => {
                    match v {
                        AdminListInviteCodesResponse::Success(v) => {
                            Ok(v)
                        }
                        AdminListInviteCodesResponse::InternalServerError(e) => {
                            Err(adminerror(format!("internal server error: {e}")).into_response())
                        }
                        AdminListInviteCodesResponse::Logout => {
                            Err(Redirect::to("/").into_response())
                        }
                        AdminListInviteCodesResponse::AccessDenied => {
                            Err(Redirect::to("/").into_response())
                        }
                    }
                }
                x => {
                    error!("auth service gave {} to our list invite codes request!", x);
                    Err(adminerror(format!("auth service gave {} to our list invite codes request!", x)).into_response())
                }
            },
        }
    }
}

async fn list_all_users(nats: Arc<jetstream::Context>, token: String) -> Result<Vec<UserSerialized>, impl IntoResponse> {
    let response = comms::query_service(
        comms::Query::AuthService(AuthServiceQuery {
            request: AuthServiceRequest::AdminListAllUsersRequest(AdminListAllUsersRequest {
                token,
            }),
            replyto: "".to_string(),
        }),
        &nats,
        false,
    ).await;
    if let Err(e) = response {
        error!("nats error: {:?}", e);
        Err(adminerror(format!("nats error: {:?}", e)).into_response())
    } else {
        let response = response.unwrap();
        match response {
            ServiceResponse::SearchService(_) => {
                error!("sent search service response when asking for auth service!! investigate ASAP!!!");
                Err(adminerror("sent search service response when asking auth service!".to_string()).into_response())
            }
            ServiceResponse::BingService(_) => {
                error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
                Err(adminerror("sent bing service response when asking auth service!".to_string()).into_response())
            }
            ServiceResponse::AuthService(r) => match r {
                AuthServiceResponse::AdminListAllUsersResponse(v) => {
                    match v {
                        AdminListAllUsersResponse::Success(v) => {
                            Ok(v)
                        }
                        AdminListAllUsersResponse::InternalServerError(e) => {
                            Err(adminerror(format!("internal server error: {e}")).into_response())
                        }
                        AdminListAllUsersResponse::Logout => {
                            Err(Redirect::to("/").into_response())
                        }
                        AdminListAllUsersResponse::AccessDenied => {
                            Err(Redirect::to("/").into_response())
                        }
                    }
                }
                x => {
                    error!("auth service gave {} to our list users request!", x);
                    Err(adminerror(format!("auth service gave {} to our list users request!", x)).into_response())
                }
            },
        }
    }
}

async fn gen_invite_code(nats: Arc<jetstream::Context>, token: String, code: String, comment: String) -> Result<(), impl IntoResponse> {
    let response = comms::query_service(
        comms::Query::AuthService(AuthServiceQuery {
            request: AuthServiceRequest::AdminInviteCodeGenerateRequest(AdminInviteCodeGenerateRequest {
                token,
                invite_code: code,
                comment,
            }),
            replyto: "".to_string(),
        }),
        &nats,
        false,
    ).await;
    if let Err(e) = response {
        error!("nats error: {:?}", e);
        Err(adminerror(format!("nats error: {:?}", e)).into_response())
    } else {
        let response = response.unwrap();
        match response {
            ServiceResponse::SearchService(_) => {
                error!("sent search service response when asking for auth service!! investigate ASAP!!!");
                Err(adminerror("sent search service response when asking auth service!".to_string()).into_response())
            }
            ServiceResponse::BingService(_) => {
                error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
                Err(adminerror("sent bing service response when asking auth service!".to_string()).into_response())
            }
            ServiceResponse::AuthService(r) => match r {
                AuthServiceResponse::AdminInviteCodeGenerateResponse(res) => {
                    match res {
                        AdminInviteCodeGenerateResponse::Success => {
                            Ok(())
                        }
                        AdminInviteCodeGenerateResponse::InternalServerError(e) => {
                            Err(adminerror(format!("internal server error: {e}")).into_response())
                        }
                        AdminInviteCodeGenerateResponse::Logout => {
                            Err(Redirect::to("/").into_response())
                        }
                        AdminInviteCodeGenerateResponse::AccessDenied => {
                            Err(Redirect::to("/").into_response())
                        }
                        AdminInviteCodeGenerateResponse::CodeAlreadyExists => {
                            Err(adminerror("code already exists".to_string()).into_response())
                        }
                    }
                }
                x => {
                    error!("auth service gave {} to our user info request!", x);
                    Err(adminerror(format!("auth service gave {} to our geninvitecode request!", x)).into_response())
                }
            },
        }
    }
}

pub async fn admin_home(
    jar: CookieJar,
    Extension(nats): Extension<Arc<jetstream::Context>>,
    Extension(opts): Extension<Opts>,
) -> impl IntoResponse {
    if let Some(token) = jar.get("token") {
        let token = token.value().to_string();
        let info = match authenticate_user(nats.clone(), token.clone()).await {
            Ok(i) => i,
            Err(e) => {
                return Redirect::to("/").into_response();
            }
        };

        if !info.administrator {
            return Redirect::to("/").into_response();
        }

        let stats = match stats(nats.clone(), token).await {
            Ok(s) => s,
            Err(e) => {
                return e.into_response();
            }
        };

        AdminHomeTemplate {
            motd: motd(),
            last_ten_users: stats.last_ten_users,
            user_count: stats.user_count,
            admin_count: stats.admin_count,
            active_verification_requests: stats.active_verification_requests,
        }.into_response()
    } else {
        Redirect::to("/").into_response()
    }
}


#[derive(Template)]
#[template(path = "admin/invitecode.html")]
pub struct AdminInviteCodeTemplate {
    motd: &'static str,

    username: String,
    active_codes: Vec<InviteCodeSerialized>,
    used_codes: Vec<InviteCodeSerialized>,
}

pub async fn admin_invitecode(
    jar: CookieJar,
    Extension(nats): Extension<Arc<jetstream::Context>>,
    Extension(opts): Extension<Opts>,
) -> impl IntoResponse {
    if let Some(token) = jar.get("token") {
        let token = token.value().to_string();
        let info = match authenticate_user(nats.clone(), token.clone()).await {
            Ok(i) => i,
            Err(e) => {
                return Redirect::to("/").into_response();
            }
        };

        if !info.administrator {
            return Redirect::to("/").into_response();
        }
        
        let active_codes = match list_invite_codes(nats.clone(), token.clone(), false).await {
            Ok(mut v) => {
                for v in &mut v {
                    if let Some(used_by) = &v.used_by {
                        if used_by.len() > 32 {
                            v.used_by = Some(format!("{}...", &used_by[0..32]));
                        }
                    }

                    if v.creator.len() > 32 {
                        v.creator = format!("{}...", &v.creator[0..32]);
                    }
                }

                v
            },
            Err(e) => {
                return e.into_response();
            }
        };
        
        let used_codes = match list_invite_codes(nats.clone(), token.clone(), true).await {
            Ok(v) => v.into_iter().map(|mut v| {
                if let Some(used_by) = &v.used_by {
                    if used_by.len() > 32 {
                        v.used_by = Some(format!("{}...", &used_by[0..32]));
                    }
                }

                if v.creator.len() > 32 {
                    v.creator = format!("{}...", &v.creator[0..32]);
                }

                if v.used_at.is_none() {
                    v.used_at = Some(String::from("unset"));
                    v
                } else {
                    v
                }
            }).collect(),
            Err(e) => {
                return e.into_response();
            }
        };

        AdminInviteCodeTemplate {
            motd: motd(),
            username: info.username,
            active_codes,
            used_codes,
        }.into_response()
    } else {
        Redirect::to("/").into_response()
    }
}

#[derive(Deserialize, Debug)]
pub struct InviteCodeForm {
    code: Option<String>,
    comment: Option<String>,
}

pub async fn admin_invitecode_post(
    jar: CookieJar,
    Extension(nats): Extension<Arc<jetstream::Context>>,
    Extension(opts): Extension<Opts>,
    Form(input): Form<InviteCodeForm>,
) -> impl IntoResponse {
    if let Some(token) = jar.get("token") {
        let token = token.value().to_string();
        let info = match authenticate_user(nats.clone(), token.clone()).await {
            Ok(i) => i,
            Err(e) => {
                return Redirect::to("/").into_response();
            }
        };

        if !info.administrator {
            return Redirect::to("/").into_response();
        }

        if input.code.is_none() || input.comment.is_none() {
            return adminerror("code and comment required".to_string()).into_response();
        }
        if input.code.as_ref().unwrap().is_empty() || input.comment.as_ref().unwrap().is_empty() {
            return adminerror("code and comment required".to_string()).into_response();
        }

        match gen_invite_code(nats.clone(), token, input.code.unwrap(), input.comment.unwrap()).await {
            Ok(_) => {
                Redirect::to("/admin/invitecode").into_response()
            }
            Err(e) => {
                e.into_response()
            }
        }
    } else {
        Redirect::to("/").into_response()
    }
}

#[derive(Template)]
#[template(path = "admin/announcement.html")]
pub struct AdminAnnouncementTemplate {
    motd: &'static str,

    error: Option<String>,

    username: String,

    slug: String,
    title: String,
    short_content: String,
    full_content: String,
}

pub async fn admin_announcement(
    jar: CookieJar,
    Extension(nats): Extension<Arc<jetstream::Context>>,
    Extension(opts): Extension<Opts>,
) -> impl IntoResponse {
    if let Some(token) = jar.get("token") {
        let token = token.value().to_string();
        let info = match authenticate_user(nats.clone(), token.clone()).await {
            Ok(i) => i,
            Err(e) => {
                return Redirect::to("/").into_response();
            }
        };

        if !info.administrator {
            return Redirect::to("/").into_response();
        }

        AdminAnnouncementTemplate {
            motd: motd(),
            error: None,
            username: info.username,
            slug: "the-post-like-this".to_string(),
            title: "Changes Happened!".to_string(),
            short_content: "we made some changes!".to_string(),
            full_content: "we made some changes! here are the details!".to_string(),
        }.into_response()
    } else {
        Redirect::to("/").into_response()
    }
}

#[derive(Deserialize, Debug, Clone)]
pub struct AnnouncementForm {
    slug: Option<String>,
    title: Option<String>,
    short_content: Option<String>,
    full_content: Option<String>,
    send_emails: Option<String>,
}

pub async fn admin_announcement_post(
    jar: CookieJar,
    Extension(nats): Extension<Arc<jetstream::Context>>,
    Extension(opts): Extension<Opts>,
    Form(input): Form<AnnouncementForm>,
) -> impl IntoResponse {
    fn announcement_error(error: &str, username: String, form: AnnouncementForm) -> impl IntoResponse {
        AdminAnnouncementTemplate {
            motd: motd(),
            error: Some(error.to_string()),
            username,
            slug: form.slug.unwrap_or_default(),
            title: form.title.unwrap_or_default(),
            short_content: form.short_content.unwrap_or_default(),
            full_content: form.full_content.unwrap_or_default(),
        }
    }

    if let Some(token) = jar.get("token") {
        let token = token.value().to_string();
        let info = match authenticate_user(nats.clone(), token.clone()).await {
            Ok(i) => i,
            Err(e) => {
                return Redirect::to("/").into_response();
            }
        };

        if !info.administrator {
            return Redirect::to("/").into_response();
        }

        if input.slug.is_none() || input.slug.as_ref().unwrap().is_empty() {
            return announcement_error("slug required", info.username, input).into_response();
        }
        if input.title.is_none() || input.title.as_ref().unwrap().is_empty() {
            return announcement_error("title required", info.username, input).into_response();
        }
        if input.short_content.is_none() || input.short_content.as_ref().unwrap().is_empty() {
            return announcement_error("short content required", info.username, input).into_response();
        }
        if input.full_content.is_none() || input.full_content.as_ref().unwrap().is_empty() {
            return announcement_error("full content required", info.username, input).into_response();
        }

        debug!("sendemails: {:?}", input.send_emails);

        let input_og = input.clone();
        let response = comms::query_service(comms::Query::AuthService(AuthServiceQuery {
            request: AuthServiceRequest::AdminCreateAnnouncementRequest(AdminCreateAnnouncementRequest {
                token: token.clone(),
                title: input.title.unwrap(),
                slug: input.slug.unwrap(),
                short_text: input.short_content.unwrap(),
                full_markdown_text: input.full_content.unwrap(),
                send_emails: !input.send_emails.unwrap_or("".to_string()).is_empty(),
            }),
            replyto: "".to_string(),
        }), &nats, false).await;

        if let Err(e) = response {
            error!("nats error: {:?}", e);
            announcement_error(&format!("nats error: {:?}", e), info.username, input_og).into_response()
        } else {
            let response = response.unwrap();
            match response {
                ServiceResponse::SearchService(_) => {
                    error!("sent search service response when asking for auth service!! investigate ASAP!!!");
                    announcement_error("auth service error", info.username, input_og).into_response()
                }
                ServiceResponse::BingService(_) => {
                    error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
                    announcement_error("auth service error", info.username, input_og).into_response()
                }
                ServiceResponse::AuthService(r) => match r {
                    AuthServiceResponse::AdminCreateAnnouncementResponse(r) => {
                        match r {
                            AdminCreateAnnouncementResponse::Success => {
                                AdminAnnouncementTemplate {
                                    motd: motd(),
                                    error: Some("success!".to_string()),
                                    username: info.username,
                                    slug: "".to_string(),
                                    title: "".to_string(),
                                    short_content: "".to_string(),
                                    full_content: "".to_string(),
                                }.into_response()
                            }
                            AdminCreateAnnouncementResponse::SlugTaken => {
                                announcement_error("slug taken!", info.username, input_og).into_response()
                            }
                            AdminCreateAnnouncementResponse::InternalServerError(e) => {
                                announcement_error(&format!("internal server error: {}", e), info.username, input_og).into_response()
                            }
                            AdminCreateAnnouncementResponse::Logout => {
                                Redirect::to("/").into_response()
                            }
                            AdminCreateAnnouncementResponse::AccessDenied => {
                                Redirect::to("/").into_response()
                            }
                        }
                    }
                    x => {
                        error!("auth survace gave {} to our create announcement request!", x);
                        announcement_error(&format!("auth service gave {x} to our create announcement request!"), info.username, input_og).into_response()
                    }
                }
            }
        }
    } else {
        Redirect::to("/").into_response()
    }
}

#[derive(Template)]
#[template(path = "admin/allusers.html")]
pub struct AdminUserListTemplate {
    motd: &'static str,

    users: Vec<UserSerialized>
}

pub async fn admin_user_list(
    jar: CookieJar,
    Extension(nats): Extension<Arc<jetstream::Context>>,
    Extension(opts): Extension<Opts>,
) -> impl IntoResponse {
    if let Some(token) = jar.get("token") {
        let token = token.value().to_string();
        let info = match authenticate_user(nats.clone(), token.clone()).await {
            Ok(i) => i,
            Err(e) => {
                return Redirect::to("/").into_response();
            }
        };

        if !info.administrator {
            return Redirect::to("/").into_response();
        }

        match list_all_users(nats.clone(), token).await {
            Ok(mut v) => {
                for v in &mut v {
                    if v.username.len() > 32 {
                        v.username = format!("{}...", &v.username[0..32]);
                    }
                }
                AdminUserListTemplate {
                    motd: motd(),
                    users: v,
                }.into_response()
            }
            Err(e) => {
                e.into_response()
            }
        }
    } else {
        Redirect::to("/").into_response()
    }
}
