use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse};
use axum::routing::get;
use axum::Router;
use axum_extra::TypedHeader;
use headers::CacheControl;
use magnetar_common::config::MagnetarConfig;
use serde::Serialize;
use serde_json::Value;
use std::sync::Arc;
use std::time::Duration;
use tera::{Context, Tera};
use tracing::error;

pub fn new_frontend_render_router(frontend_renderer_config: FrontendConfig) -> Router {
    Router::new()
        .route(
            "/*path",
            get(render_frontend).with_state(frontend_renderer_config.clone()),
        )
        .route(
            "/",
            get(render_frontend).with_state(frontend_renderer_config),
        )
}

#[derive(Debug, Clone)]
pub struct FrontendConfig {
    pub magnetar_config: &'static MagnetarConfig,
    pub templater: Arc<Tera>,
}

#[derive(Debug, Clone, Serialize)]
struct ClientEntryPoint {
    file: String,
    css: Vec<String>,
}

pub async fn render_frontend(
    State(FrontendConfig {
        magnetar_config,
        templater,
    }): State<FrontendConfig>,
) -> Result<impl IntoResponse, StatusCode> {
    let mut context = Context::new();
    // TODO: Better title
    context.insert("title", &magnetar_config.branding.name);
    context.insert("app_name", &magnetar_config.branding.name);
    context.insert("version", &magnetar_config.branding.version);

    context.insert(
        "boot_js",
        &std::fs::read_to_string("fe_calckey/frontend/assets-be/template/boot.js").map_err(
            |e| {
                error!("Failed to read boot JS: {:?}", e);
                StatusCode::INTERNAL_SERVER_ERROR
            },
        )?,
    );
    context.insert(
        "style_css",
        &std::fs::read_to_string("fe_calckey/frontend/assets-be/template/style.css").map_err(
            |e| {
                error!("Failed to read boot CSS: {:?}", e);
                StatusCode::INTERNAL_SERVER_ERROR
            },
        )?,
    );

    context.insert(
        "base_url",
        &format!("https://{}", magnetar_config.networking.host),
    );

    context.insert("instance_host", &magnetar_config.networking.host);
    // TODO: Actual name
    context.insert("instance_name", &magnetar_config.networking.host);
    // TODO: Add to config or pull from the backend
    context.insert("robots", &true);

    let manifest = std::fs::read_to_string("fe_calckey/frontend/built/_client_dist_/manifest.json")
        .map_err(|e| {
            error!("Failed to read manifest: {:?}", e);
            StatusCode::INTERNAL_SERVER_ERROR
        })?;

    let manifest_json: Value = serde_json::from_str(&manifest).map_err(|e| {
        error!("Failed to parse manifest: {:?}", e);
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    let entry_point = manifest_json.get("src/init.ts").ok_or_else(|| {
        error!("Missing entry point in manifest");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    context.insert(
        "client_entry",
        &ClientEntryPoint {
            file: entry_point
                .get("file")
                .and_then(Value::as_str)
                .ok_or_else(|| {
                    error!("Missing entry point file in manifest");
                    StatusCode::INTERNAL_SERVER_ERROR
                })?
                .to_owned(),
            css: entry_point
                .get("css")
                .and_then(Value::as_array)
                .map_or_else(Vec::new, |css| {
                    css.iter()
                        .filter_map(Value::as_str)
                        .map(|s| s.to_owned())
                        .collect::<Vec<String>>()
                }),
        },
    );
    context.insert(
        "timestamp",
        &format!("v={}", &chrono::Utc::now().timestamp()),
    );

    let html = match templater.render("base.html", &context) {
        Ok(html) => html,
        Err(e) => {
            error!("Failed to render template: {:?}", e);
            return Err(StatusCode::INTERNAL_SERVER_ERROR);
        }
    };

    Ok((
        TypedHeader(
            CacheControl::new()
                .with_public()
                .with_max_age(Duration::from_secs(3)),
        ),
        Html(html),
    ))
}
