use axum::http::{
	HeaderMap, HeaderValue, StatusCode,
	header::{self},
};
use chrono::TimeDelta;
use maud::{Markup, Render, html};
use serde::Deserialize;
use std::pin::Pin;

use crate::{Rendered, RequestContext, Servable};

//
// MARK: metadata
//

#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize)]
pub struct PageMetadata {
	pub title: String,
	pub author: Option<String>,
	pub description: Option<String>,
	pub image: Option<String>,
	pub backlinks: Option<bool>,
}

impl Default for PageMetadata {
	fn default() -> Self {
		Self {
			title: "Untitled page".into(),
			author: None,
			description: None,
			image: None,
			backlinks: None,
		}
	}
}

impl Render for PageMetadata {
	fn render(&self) -> Markup {
		let empty = String::new();
		let title = &self.title;
		let author = &self.author.as_ref().unwrap_or(&empty);
		let description = &self.description.as_ref().unwrap_or(&empty);
		let image = &self.image.as_ref().unwrap_or(&empty);

		html !(
			meta property="og:site_name" content=(title) {}
			meta name="title" content=(title) {}
			meta property="og:title" content=(title) {}
			meta property="twitter:title" content=(title) {}

			meta name="author" content=(author) {}

			meta name="description" content=(description) {}
			meta property="og:description" content=(description) {}
			meta property="twitter:description" content=(description) {}

			meta content=(image) property="og:image" {}
			link rel="shortcut icon" href=(image) type="image/x-icon" {}
		)
	}
}

//
// MARK: page
//

// Some HTML
pub struct Page {
	pub meta: PageMetadata,

	/// How long this page's html may be cached.
	/// This controls the maximum age of a page shown to the user.
	///
	/// If `None`, this page is always rendered from scratch.
	pub html_ttl: Option<TimeDelta>,

	/// A function that generates this page's html.
	///
	/// This should return the contents of this page's <body> tag,
	/// or the contents of a wrapper element (defined in the page server struct).
	///
	/// This closure must never return `<html>` or `<head>`.
	pub generate_html: Box<
		dyn Send
			+ Sync
			+ for<'a> Fn(
				&'a Page,
				&'a RequestContext,
			) -> Pin<Box<dyn Future<Output = Markup> + 'a + Send + Sync>>,
	>,
}

impl Default for Page {
	fn default() -> Self {
		Page {
			meta: Default::default(),
			html_ttl: Some(TimeDelta::seconds(60 * 24 * 30)),
			//css_ttl: Duration::from_secs(60 * 24 * 30),
			//generate_css: None,
			generate_html: Box::new(|_, _| Box::pin(async { html!() })),
		}
	}
}

impl Page {
	pub async fn generate_html(&self, ctx: &RequestContext) -> Markup {
		(self.generate_html)(self, ctx).await
	}
}

impl Servable for Page {
	fn render<'a>(
		&'a self,
		ctx: &'a RequestContext,
	) -> Pin<Box<dyn Future<Output = crate::Rendered> + 'a + Send + Sync>> {
		Box::pin(async {
			let mut headers = HeaderMap::with_capacity(3);
			let html = self.generate_html(ctx).await;

			headers.append(
				header::CONTENT_TYPE,
				HeaderValue::from_static("text/html; charset=utf-8"),
			);

			return Rendered {
				code: StatusCode::OK,
				headers,
				body: html.0.into_bytes(),
				ttl: self.html_ttl,
			};
		})
	}
}
