use std::path::Path;
use std::process::exit;
use std::sync::Arc;

use axum::response::{Html, IntoResponse};
use serde::Serialize;
use tera::{Tera, Context};

use crate::errors::Error;


#[derive(Clone)]
pub struct TemplateEngine {
    tera: Arc<Tera>,
}

pub enum TemplateError {
    SerializationError { reason: Box<dyn std::error::Error> },
    RenderError { name: String, reason: Box<dyn std::error::Error> },
}

impl TemplateEngine {
    pub fn new(template_directory: &Path) -> Self {
        let template_glob = template_directory.join("**").join("*");
        match Tera::new(template_glob.to_str().expect("valid glob path string")) {
            Ok(tera) => TemplateEngine { tera: Arc::new(tera) },
            Err(e) => {
                println!("Loading templates failed: {}", e);
                exit(1)
            }
        }
    }

    pub fn render<S: Serialize>(&self, name: &str, context: S) -> Result<String, TemplateError> {
        let context = Context::from_serialize(context).map_err(|e| {
            TemplateError::SerializationError { reason: Box::new(e) }
        })?;

        let content = self.tera.render(name, &context).map_err(|e| {
            TemplateError::RenderError { name: name.into(), reason: Box::new(e) }
        })?;


        Ok(content)
    }
}

pub struct Template<'n, S: Serialize> {
    pub name: &'n str,
    pub engine: TemplateEngine,
    pub context: S,
}

impl<'n, S: Serialize> Template<'n, S> {
    pub fn new(name: &'n str, engine: TemplateEngine, context: S) -> Self {
        Template {
            name,
            engine,
            context,
        }
    }
}

impl<S: Serialize> IntoResponse for Template<'_, S> {
    fn into_response(self) -> axum::response::Response {
        let res = self.engine.render(self.name, self.context);

        match res {
            Ok(content) => Html(content).into_response(),
            Err(err) => Error::from(err).into_response(),
        }
    }
}