use std::{cmp::Ordering, fs};

use anyhow::{bail, Context};
use orgize::{elements::Keyword, export::HtmlHandler, Element};
use time::{macros::format_description, Date};

use crate::{
    date::parse_org_date,
    load::Loadable,
    parser::{parse, CustomHtmlHandler, Parser},
    util::slugify,
};

#[derive(Eq)]
pub struct Post {
    pub title: String,
    pub date: Option<Date>,
    pub index: u32,
    pub content: String,
    pub url: String,
}

impl Post {
    pub fn html(&self) -> anyhow::Result<String> {
        let title = &self.title;
        let content = &self.content;
        let date = &self.date_f()?;

        let mut out = format!(r#"<div class="header-container"><h1>{title}</h1>"#);

        if let Some(date) = date {
            out.push_str(r#"<div class="header-date">"#);
            out.push_str(date);
            out.push_str("</div>");
        }

        out.push_str("</div>");
        out.push_str(content);

        Ok(out)
    }

    pub fn link(&self) -> anyhow::Result<String> {
        let link = match self.date_f()? {
            Some(date_f) => format!(
                r#"<tr><td><a href="{}">{}</a></td><td>[{}]</td></tr>"#,
                self.url, self.title, date_f
            ),
            None => format!(
                r#"<tr><td><a href="{}">{}</a></td><td></td></tr>"#,
                self.url, self.title
            ),
        };

        Ok(link)
    }

    pub fn link_short(&self) -> String {
        format!(
            r#"<a href="{}" class="page-link">{}</a>"#,
            self.url, self.title
        )
    }

    fn date_f(&self) -> anyhow::Result<Option<String>> {
        let date_f = match self.date {
            Some(date) => date.format(format_description!("[day] [month repr:short] [year]"))?,
            None => return Ok(None),
        };

        Ok(Some(date_f))
    }
}

impl Loadable for Post {
    fn load(path: &std::path::Path, slug_prefix: &str) -> anyhow::Result<Self> {
        let file_name = path
            .file_stem()
            .context("Could not get file name")?
            .to_str()
            .context("Could not convert filename into string")?
            .to_owned();

        let url = slugify(&format!("/{slug_prefix}/{file_name}"));

        parse(PostParser::new(url)?, &fs::read_to_string(path)?)
    }
}

impl Ord for Post {
    fn cmp(&self, other: &Self) -> Ordering {
        let mut o = self.index.cmp(&other.index);
        if o.is_eq() {
            o = self.date.cmp(&other.date).reverse();
        }

        o
    }
}

impl PartialOrd for Post {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for Post {
    fn eq(&self, other: &Self) -> bool {
        self.index == other.index && self.date == other.date
    }
}

struct PostParser {
    title: Option<String>,
    date: Option<Date>,
    index: Option<u32>,
    url: String,
    content: Vec<u8>,
    handler: CustomHtmlHandler,
}

impl PostParser {
    fn new(url: String) -> anyhow::Result<Self> {
        Ok(Self {
            title: None,
            date: None,
            index: None,
            url,
            content: vec![],
            handler: CustomHtmlHandler::new()?,
        })
    }
}

impl Parser<Post> for PostParser {
    fn start(&mut self, e: &Element) -> anyhow::Result<()> {
        match e {
            Element::Keyword(Keyword { key, value, .. }) if key == "TITLE" => {
                if self.title.is_some() {
                    bail!("Post has more than one title");
                }

                self.title = Some(value.to_string());
            }

            Element::Keyword(Keyword { key, value, .. }) if key == "DATE" => {
                if self.date.is_some() {
                    bail!("Post has more than one date");
                }

                self.date = Some(parse_org_date(value)?);
            }

            Element::Keyword(Keyword { key, value, .. }) if key == "INDEX" => {
                if self.index.is_some() {
                    bail!("Post has more than one index");
                }

                self.index = Some(value.parse()?);
            }

            _ => {
                self.handler.start(&mut self.content, e)?;
            }
        }

        Ok(())
    }

    fn end(&mut self, e: &Element) -> anyhow::Result<()> {
        self.handler.end(&mut self.content, e)?;

        Ok(())
    }

    fn render(self) -> anyhow::Result<Post> {
        let title = self.title.context("Could not find post title")?;
        let date = self.date;
        let index = self.index.unwrap_or(u32::MAX);
        let content = String::from_utf8(self.content)?;
        let url = self.url;

        Ok(Post {
            title,
            date,
            index,
            content,
            url,
        })
    }
}