/*
 * asklyphe-frontend routes/index.rs
 * - http routes for homepages
 *
 * 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 std::sync::atomic::Ordering;
use askama::Template;
use askama_axum::IntoResponse;
use asklyphe_common::nats::authservice::{AuthError, AuthRequest, AuthResponse, AuthServiceQuery, AuthServiceRequest, AuthServiceResponse, LogoutRequest, LogoutResponse};
use asklyphe_common::nats::authservice::admin::{Stats, StatsRequest, StatsResponse};
use asklyphe_common::nats::authservice::announcements::{LatestAnnouncementRequest, LatestAnnouncementResponse};
use asklyphe_common::nats::authservice::profile::{UserInfoRequest, UserInfoResponse};
use asklyphe_common::nats::comms;
use asklyphe_common::nats::comms::{ServiceError, ServiceResponse};
use async_nats::jetstream;
use axum::Extension;
use axum::response::Redirect;
use axum_extra::extract::CookieJar;
use serde::Serialize;
use tokio::sync::Mutex;
use tracing::error;
use tracing::log::warn;
use crate::{BUILT_ON, GIT_COMMIT, Opts, ALPHA, VERSION, WEBSITE_COUNT, YEAR};
use crate::routes::{authenticate_user, UserInfo};

#[derive(Serialize, Debug)]
pub struct FrontpageAnnouncement {
    pub url: String,
    pub title: String,
    pub content: String,
    pub date: String,
}

async fn latest_announcement(nats: Arc<jetstream::Context>) -> Option<FrontpageAnnouncement> {
    let response = comms::query_service(
        comms::Query::AuthService(AuthServiceQuery {
            request: AuthServiceRequest::LatestAnnouncementRequest(LatestAnnouncementRequest {}),
            replyto: "".to_string(),
        }),
        &nats,
        false,
    ).await;
    if let Err(e) = response {
        error!("nats error: {:?}", e);
        None
    } else {
        let response = response.unwrap();
        match response {
            ServiceResponse::SearchService(_) => {
                error!("sent search service response when asking for auth service!! investigate ASAP!!!");
                None
            }
            ServiceResponse::BingService(_) => {
                error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
                None
            }
            ServiceResponse::AuthService(r) => match r {
                AuthServiceResponse::LatestAnnouncementResponse(v) => {
                    match v {
                        LatestAnnouncementResponse::Some(v) => {
                            Some(FrontpageAnnouncement {
                                url: format!("/announcements/{}", v.slug),
                                title: v.title,
                                content: v.short_text,
                                date: chrono::DateTime::from_timestamp(v.date, 0).unwrap().to_rfc2822(),
                            })
                        }
                        LatestAnnouncementResponse::None => {
                            None
                        }
                        LatestAnnouncementResponse::InternalServerError(e) => {
                            warn!("internal server error during latest announcement request! {e}");
                            None
                        }
                    }
                }
                x => {
                    error!("auth service gave {} to our user info request!", x);
                    None
                }
            },
        }
    }
}

pub fn frontpage_error(error: &str, auth_url: String) -> FrontpageTemplate {
    FrontpageTemplate {
        authurl: auth_url,
        error: Some(error.to_string()),
        version: VERSION.to_string(),
        git_commit: GIT_COMMIT.to_string(),
        built_on: BUILT_ON.to_string(),
        year: YEAR.to_string(),
        alpha: ALPHA,
        count: WEBSITE_COUNT.load(Ordering::Relaxed),
        theme: "default".to_string(),
        announcement: None,
    }
}

pub async fn logout(
    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 response = comms::query_service(
            comms::Query::AuthService(AuthServiceQuery {
                request: AuthServiceRequest::LogoutRequest(LogoutRequest {
                    token,
                }),
                replyto: "".to_string(),
            }),
            &nats,
            false,
        ).await;
        if let Err(e) = response {
            error!("nats error: {:?}", e);
            frontpage_error("internal server error", opts.auth_url.clone()).into_response()
        } 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::LogoutResponse(_) => {}
                    x => {
                        error!("auth service gave {} to our user info request!", x);
                        internal_server_error = true;
                    }
                }
            }
            if internal_server_error {
                frontpage_error("internal server error", opts.auth_url.clone()).into_response()
            } else {
                match response {
                    ServiceResponse::AuthService(AuthServiceResponse::LogoutResponse(r)) => match r {
                        LogoutResponse::Success => {
                            (jar.remove("token"),
                             Redirect::to("/").into_response()).into_response()
                        }
                        LogoutResponse::InternalServerError(e) => {
                            error!("internal server error {e} returned from auth service during logout! investigate ASAP!!!");
                            frontpage_error("internal server error", opts.auth_url.clone()).into_response()
                        }
                        LogoutResponse::BadTokenError => {
                            (jar.remove("token"),
                             Redirect::to("/").into_response()).into_response()
                        }
                    }
                    _ => unreachable!()
                }
            }
        }
    } else {
        Redirect::to("/").into_response()
    }
}

#[derive(Template)]
#[template(path = "frontpage.html")]
pub struct FrontpageTemplate {
    authurl: String,
    error: Option<String>,
    version: String,
    git_commit: String,
    built_on: String,
    year: String,
    alpha: bool,
    count: u64,
    theme: String,
    announcement: Option<FrontpageAnnouncement>,
}

pub async fn frontpage(
    Extension(nats): Extension<Arc<jetstream::Context>>,
    Extension(opts): Extension<Opts>) -> impl IntoResponse {
    let announcement = latest_announcement(nats.clone()).await;
    FrontpageTemplate {
        authurl: opts.auth_url.clone(),
        error: None,
        version: VERSION.to_string(),
        git_commit: GIT_COMMIT.to_string(),
        built_on: BUILT_ON.to_string(),
        year: YEAR.to_string(),
        alpha: ALPHA,
        count: WEBSITE_COUNT.load(Ordering::Relaxed),
        theme: "default".to_string(),
        announcement,
    }
}

#[derive(Template)]
#[template(path = "home.html")]
struct IndexTemplate {
    info: UserInfo,
    version: String,
    git_commit: String,
    built_on: String,
    year: String,
    alpha: bool,
    count: u64,
    theme: String,
    announcement: Option<FrontpageAnnouncement>,
}

pub async fn index(
    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).await {
            Ok(i) => i,
            Err(e) => {
                return (jar.remove("token"), frontpage_error(e.as_str(), opts.auth_url.clone())).into_response();
            }
        };
        let theme = info.theme.clone();
        
        let announcement = latest_announcement(nats.clone()).await;
        
        IndexTemplate {
            info,
            version: VERSION.to_string(),
            git_commit: GIT_COMMIT.to_string(),
            built_on: BUILT_ON.to_string(),
            year: YEAR.to_string(),
            alpha: ALPHA,
            count: WEBSITE_COUNT.load(Ordering::Relaxed),
            theme,
            announcement,
        }.into_response()
    } else {
        let announcement = latest_announcement(nats.clone()).await;
        FrontpageTemplate {
            authurl: opts.auth_url.clone(),
            error: None,
            version: VERSION.to_string(),
            git_commit: GIT_COMMIT.to_string(),
            built_on: BUILT_ON.to_string(),
            year: YEAR.to_string(),
            alpha: ALPHA,
            count: WEBSITE_COUNT.load(Ordering::Relaxed),
            theme: "default".to_string(),
            announcement,
        }.into_response()
    }
}
