/*
 * asklyphe-frontend routes/mod.rs
 * - module file for http routes + some commonly used 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::fmt::Display;
use std::str::FromStr;
use std::sync::Arc;
use askama::Template;
use askama_axum::IntoResponse;
use asklyphe_common::nats::authservice::{AuthServiceQuery, AuthServiceRequest, AuthServiceResponse};
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::http::StatusCode;
use serde::Serialize;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use time::macros::utc_datetime;
use time::{OffsetDateTime, UtcDateTime};
use tracing::{debug, error};

const RANDOM_THEME_EPOCH: UtcDateTime = utc_datetime!(2025-03-19 00:00);

pub mod search;
pub mod index;
pub mod semaphore;
pub mod user_settings;
pub mod admin;
pub mod announcement;

#[derive(Default, EnumIter, PartialEq, Eq, Copy, Clone)]
pub enum Themes {
    Classic,
    Dark,
    #[default]
    Default,
    Freaky,
    Gloss,
    Oled,
    Water,
    Random
}

impl Themes {
    pub fn get_all_themes() -> Vec<Themes> {
        Self::iter().collect()
    }

    pub fn display_name(&self) -> String {
        match self {
            Themes::Classic => {
                "classic".to_string()
            }
            Themes::Dark => {
                "dark theme".to_string()
            }
            Themes::Default => {
                "default theme".to_string()
            }
            Themes::Freaky => {
                "freaky".to_string()
            }
            Themes::Gloss => {
                "gloss".to_string()
            }
            Themes::Oled => {
                "lights out".to_string()
            }
            Themes::Water => {
                "water".to_string()
            }
            Themes::Random => {
                "random".to_string()
            }
        }
    }

    pub fn internal_name(&self) -> String {
        match self {
            Themes::Classic => {
                "classic".to_string()
            }
            Themes::Dark => {
                "dark".to_string()
            }
            Themes::Default => {
                "default".to_string()
            }
            Themes::Freaky => {
                "freaky".to_string()
            }
            Themes::Gloss => {
                "gloss".to_string()
            }
            Themes::Oled => {
                "oled".to_string()
            }
            Themes::Water => {
                "water".to_string()
            }
            Themes::Random => {
                "random".to_string()
            }
        }
    }
}

impl FromStr for Themes {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "classic" => Ok(Themes::Classic),
            "dark" => Ok(Themes::Dark),
            "default" => Ok(Themes::Default),
            "freaky" => Ok(Themes::Freaky),
            "gloss" => Ok(Themes::Gloss),
            "oled" => Ok(Themes::Oled),
            "water" => Ok(Themes::Water),
            "random" => Ok(Themes::Random),
            _ => Err(())
        }
    }
}

#[derive(Serialize, Clone)]
pub struct UserInfo {
    pub username: String,
    pub email: String,
    pub new_email: Option<String>,
    pub theme: String,
    pub administrator: bool,
}

impl UserInfo {
    pub fn get_theme(&self) -> Themes {
        let theme: Themes = self.theme.parse().unwrap_or_default();

        if theme.eq(&Themes::Random) {
            let possible_themes = Themes::get_all_themes();
            let current_day = UtcDateTime::now();

            let rand_value = (((current_day - RANDOM_THEME_EPOCH).as_seconds_f64() / 86400.0) % possible_themes.len() as f64) as usize;
            
            *possible_themes.get(rand_value).unwrap_or(&Themes::Default)
        } else {
            theme
        }
    }
    
    pub fn get_true_theme(&self) -> Themes {
        self.theme.parse().unwrap()
    }
}

pub async fn authenticate_user(nats: Arc<jetstream::Context>, token: String) -> Result<UserInfo, String> {
    let response = comms::query_service(
        comms::Query::AuthService(AuthServiceQuery {
            request: AuthServiceRequest::UserInfoRequest(UserInfoRequest {
                token,
            }),
            replyto: "".to_string(),
        }),
        &nats,
        false,
    ).await;
    if let Err(e) = response {
        error!("nats error: {:?}", e);
        Err("internal server error".to_string())
    } else {
        let response = response.unwrap();
        let mut internal_server_error = false;
        match &response {
            ServiceResponse::SearchService(_) => {
                error!("sent search service response when asking for auth service!! investigate ASAP!!!");
                internal_server_error = true;
            }
            ServiceResponse::BingService(_) => {
                error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
                internal_server_error = true;
            }
            ServiceResponse::AuthService(r) => match r {
                AuthServiceResponse::UserInfoResponse(_) => {}
                x => {
                    error!("auth service gave {} to our user info request!", x);
                    internal_server_error = true;
                }
            },
        }
        let info = if internal_server_error {
            return Err("internal server error".to_string());
        } else {
            match response {
                ServiceResponse::AuthService(AuthServiceResponse::UserInfoResponse(r)) => match r {
                    UserInfoResponse::Success(ui) => {
                        UserInfo {
                            username: ui.username,
                            email: ui.email,
                            new_email: ui.new_email,
                            theme: ui.theme,
                            administrator: ui.administrator,
                        }
                    }
                    UserInfoResponse::InternalServerError(e) => {
                        error!("internal server error while querying user data! {e}");
                        return Err("internal server error".to_string());
                    }
                    UserInfoResponse::TokenExpired => {
                        return Err("your session has expired! please log in and try again!".to_string());
                    }
                    UserInfoResponse::TokenInvalid => {
                        return Err("your session has expired! please log in and try again!".to_string());
                    }
                    UserInfoResponse::AccountSuspended => {
                        return Err("unfortunately your account has been suspended, please check our support pages for more info".to_string());
                    }
                }
                _ => unreachable!()
            }
        };
        Ok(info)
    }
}

#[derive(Template)]
#[template(path = "404.html")]
pub struct NotFoundTemplate;

pub async fn not_found() -> impl IntoResponse {
    (StatusCode::NOT_FOUND, NotFoundTemplate).into_response()
}
