//! atproto-static-web   A simple web viewer for AT Proto.
//! Copyright (C) 2025  AverageHelper
//!
//! This program is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by
//! the Free Software Foundation, either version 3 of the License, or
//! (at your option) any later version.
//!
//! 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 General Public License for more details.
//!
//! You should have received a copy of the GNU General Public License
//! along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::{
	env::Env,
	server::lang::Lang,
	templates,
	xrpc::{self, get_post_thread::GetPostThreadError, get_profile::GetProfileError},
};
use log::debug;
use maud::Markup;
use reqwest::Client;
use rocket::{
	State, catch, get,
	http::{ContentType, Status},
	response::status::NotFound,
};
use rust_embed::Embed;
use std::{borrow::Cow, ffi::OsStr, path::PathBuf};

#[get("/")]
pub fn index(lang: Lang, env: &State<Env>) -> Markup {
	templates::index::index(lang, env)
}

#[get("/favicon.ico")]
pub fn favicon() -> NotFound<()> {
	NotFound(())
}

#[get("/robots.txt")]
pub fn robots() -> &'static str {
	"User-agent: *
Disallow: /"
}

#[derive(Embed)]
#[folder = "src/static/fonts/"]
struct Fonts;

#[get("/static/fonts/<file..>")]
pub fn fonts(file: PathBuf) -> Option<(ContentType, Cow<'static, [u8]>)> {
	let filename = file.display().to_string();
	let asset = Fonts::get(&filename)?;
	let content_type = file
		.extension()
		.and_then(OsStr::to_str)
		.and_then(ContentType::from_extension)
		.unwrap_or(ContentType::Bytes);

	Some((content_type, asset.data))
}

#[get("/profile/<profile>")]
pub async fn profile(
	lang: Lang,
	client: &State<Client>,
	env: &State<Env>,
	profile: &str,
) -> Result<Markup, Status> {
	let profile = match xrpc::get_profile(client, env, profile).await {
		Ok(profile) => profile,
		Err(GetProfileError::Deserialize(err)) => {
			debug!("could not parse response: {err:?}");
			return Err(Status::InternalServerError);
		}
		Err(GetProfileError::Network(err)) => {
			debug!("could not send request: {err:?}");
			return Err(Status::InternalServerError);
		}
		Err(GetProfileError::InvalidAuthority(err)) => {
			debug!("could not parse authority to send: {err:?}");
			return Err(Status::NotFound);
		}
		Err(GetProfileError::Upstream(status, message)) => {
			debug!("↗ {status}: {message}");
			return Err(Status::from_code(status.as_u16()).expect("valid status"));
		}
	};

	let pinned = match &profile.pinned_post {
		None => None,
		Some(pinned) => match xrpc::get_post_with_uri(client, env, &pinned.uri).await {
			Ok(thread) => Some(Ok(thread)),
			Err(err) => Some(Err(err)),
		},
	};
	Ok(templates::profile::profile(lang, env, profile, pinned))
}

// TODO: #[get("/profile/<profile>/followers")]
// pub async fn followers() {}

// TODO: #[get("/profile/<profile>/follows")]
// pub async fn follows() {}

// TODO: #[get("/search?<q>")]
// pub async fn search(q: &str) {}

#[get("/profile/<profile>/post/<post>")]
pub async fn post(
	lang: Lang,
	client: &State<Client>,
	env: &State<Env>,
	profile: &str,
	post: &str,
) -> Result<Markup, Status> {
	let thread = match xrpc::get_post_thread(client, env, profile, post).await {
		Ok(thread) => thread,
		Err(GetPostThreadError::Deserialize(err)) => {
			debug!("could not parse response: {err:?}");
			return Err(Status::InternalServerError);
		}
		Err(GetPostThreadError::Network(err)) => {
			debug!("could not send request: {err:?}");
			return Err(Status::InternalServerError);
		}
		Err(GetPostThreadError::InvalidAuthority(err)) => {
			debug!("could not parse authority to send: {err:?}");
			return Err(Status::NotFound);
		}
		Err(GetPostThreadError::InvalidRecordKey(err)) => {
			debug!("could not parse rkey to send: {err:?}");
			return Err(Status::NotFound);
		}
		Err(GetPostThreadError::Upstream(status, message)) => {
			debug!("↗ {status}: {message}");
			return Err(Status::from_code(status.as_u16()).expect("valid status"));
		}
	};
	Ok(templates::thread::thread(lang, env, thread))
}

pub mod catchers {
	use super::*;

	#[catch(default)]
	pub async fn error(status: Status, req: &rocket::Request<'_>) -> (Status, Markup) {
		let lang: Lang = req.guard().await.expect("lang never fails");
		let markup = templates::error::error(lang, status);
		(status, markup)
	}
}
