mod schema;

extern crate diesel;

#[macro_use]
extern crate rocket;

use codigocomentado::*;
use comrak::{markdown_to_html, Options};
use controllers::posts;
use dotenv::dotenv;
use isbot::Bots;
use rocket::fs::{FileServer, NamedFile};
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome, Request};
use rocket::response::content::RawXml;
use rocket::tokio::sync::OnceCell;
use rocket_dyn_templates::Template;
use std::path::Path;
use std::vec::Vec;

static BOTS: OnceCell<Bots> = OnceCell::const_new();

async fn is_bot(ua: &str) -> bool {
    let bots = BOTS.get_or_init(|| async { Bots::default() }).await;
    bots.is_bot(ua)
}

struct Headers {
    user_agent: String,
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for Headers {
    type Error = ();

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        let token = request.headers().get_one("User-Agent");
        match token {
            Some(token) => Outcome::Success(Headers {
                user_agent: token.to_string(),
            }),
            None => Outcome::Error((Status::Unauthorized, ())),
        }
    }
}

#[get("/?<page>")]
fn index(page: Option<u64>) -> Template {
    let page: u64 = page.unwrap_or(1);

    let (posts, n_posts) = posts::get_posts(Some(page));

    let context = contexts::PageOfPosts::new(posts, page, n_posts);

    Template::render("index", context)
}

#[get("/feed.xml")]
fn rss_feed() -> RawXml<Template> {
    let (mut posts, _) = posts::get_posts(None);

    let mut comrak_options = Options::default();
    comrak_options.extension.table = true;
    comrak_options.extension.autolink = true;
    comrak_options.extension.tasklist = true;
    comrak_options.render.unsafe_ = true;

    posts.iter_mut().for_each(|post| {
        let content = markdown_to_html(&post.content, &comrak_options);
        post.content = content;
    });

    let context = contexts::XmlFeed::new(posts);

    RawXml(Template::render("rss", context))
}

#[get("/atom.xml")]
fn atom_feed() -> RawXml<Template> {
    let (mut posts, _) = posts::get_posts(None);

    let mut comrak_options = Options::default();
    comrak_options.extension.table = true;
    comrak_options.extension.autolink = true;
    comrak_options.extension.tasklist = true;
    comrak_options.render.unsafe_ = true;

    posts.iter_mut().for_each(|post| {
        let content = markdown_to_html(&post.content, &comrak_options);
        post.content = content;
    });

    let context = contexts::XmlFeed::new(posts);

    RawXml(Template::render("atom", context))
}

#[get("/post/<title>")]
async fn show_post(title: &str, headers: Headers) -> Template {
    let title_splited: Vec<&str> = title.split('-').collect();
    let id = title_splited.last().unwrap().parse().unwrap_or(-1);

    match posts::get_post(id) {
        Ok(mut post) => {
            if !is_bot(&headers.user_agent).await {
                posts::add_visit(id);
            }

            let mut comrak_options = Options::default();
            comrak_options.extension.table = true;
            comrak_options.extension.autolink = true;
            comrak_options.extension.tasklist = true;
            comrak_options.render.unsafe_ = true;
            comrak_options.extension.header_ids = Some("id-".to_string());

            let content = markdown_to_html(&post.content, &comrak_options);
            post.content = content;

            let context = contexts::SinglePost::new(post);

            Template::render("post", context)
        }
        Err(_) => {
            let url = format!("/post/{}", title_splited.join("-"));

            let context = contexts::NotFound::new(url);

            Template::render("404", context)
        }
    }
}

#[catch(404)]
fn not_found_404(req: &Request) -> Template {
    let uri = format!("{}", req.uri());

    let context = contexts::NotFound::new(uri);

    Template::render("404", context)
}

#[get("/favicon.ico")]
async fn favicon() -> Option<NamedFile> {
    NamedFile::open(Path::new("static/favicon.ico")).await.ok()
}

#[launch]
fn rocket() -> _ {
    dotenv().ok();

    rocket::build()
        .attach(Template::fairing())
        .mount("/", routes![index, show_post, favicon, rss_feed, atom_feed])
        .mount("/admin", admin::get_routes())
        .mount("/static", FileServer::from("static"))
        .register("/", catchers![not_found_404])
}