use crate::database::PsqlData;
use futures_util::TryStreamExt;
use serde::{Deserialize, Serialize};
use sqlx::{postgres::PgPool, Pool, Postgres};
use std::{error::Error, path::Path};
use std::{
    fs::{self, metadata},
    path::PathBuf,
};
use time::{macros::format_description, Date};

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Page {
    pub id: i32,
    pub reference: String,
    pub title: String,
    pub content: String,
    pub date_created: Date,
    pub date_last_updated: Date,
    pub description: Option<String>,
}

impl Page {
    pub fn from_file<P>(file_name: P) -> Self
    where
        P: AsRef<Path>,
    {
        let mut reference: Option<String> = None;
        let mut title: Option<String> = None;
        let mut date_created: Option<Date> = None;
        let mut date_last_updated: Option<Date> = None;
        let mut description: Option<String> = None;
        let mut lines: Vec<String> = super::read_lines(file_name);
        lines.reverse();
        while lines.len() > 0 {
            let line: String = lines.pop().expect("Something went terribly wrong here");
            if line.contains("id: ") {
                reference = Some(line.clone().replace("id: ", ""));
            } else if line.contains("title: ") {
                title = Some(line.clone().replace("title: ", ""));
            } else if line.contains("date_created: ") {
                let format = format_description!("[year]-[month]-[day]");
                date_created = Some(
                    Date::parse(&line.clone().replace("date_created: ", ""), format)
                        .expect("not a date"),
                );
            } else if line.contains("date_last_updated: ") {
                let format = format_description!("[year]-[month]-[day]");
                date_last_updated = Some(
                    Date::parse(&line.clone().replace("date_last_updated: ", ""), format)
                        .expect("not a date"),
                );
            } else if line.contains("description: ") {
                description = Some(line.clone().replace("description: ", ""));
            } else if line.eq("---") {
                break;
            }
        }
        lines.reverse();
        let contents: String = lines.join("\n");
        Page {
            id: 0,
            reference: reference.unwrap(),
            title: title.unwrap(),
            content: contents,
            date_created: date_created.unwrap(),
            date_last_updated: date_last_updated.unwrap(),
            description,
        }
    }

    pub async fn read_by_reference(
        pool: &PgPool,
        reference: &String,
    ) -> Result<Box<Self>, Box<dyn Error>> {
        let result = sqlx::query_as!(Self, "SELECT * FROM pages WHERE reference = $1", reference)
            .fetch_one(pool)
            .await?;

        Ok(Box::new(result))
    }
}

impl PsqlData for Page {
    async fn read_all(pool: &PgPool) -> Result<Vec<Box<Self>>, Box<dyn Error>> {
        crate::psql_read_all!(Self, pool, "pages")
    }

    async fn read(pool: &PgPool, id: i32) -> Result<Box<Self>, Box<dyn Error>> {
        crate::psql_read!(Self, pool, id, "pages")
    }

    async fn insert(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
        sqlx::query!(
            "INSERT INTO pages (reference, title, content, date_created, date_last_updated, description) VALUES ($1, $2, $3, $4, $5, $6)",
            self.reference,
            self.title,
            self.content,
            self.date_created,
            self.date_last_updated,
            self.description,
        )
        .execute(pool)
        .await?;

        Ok(())
    }

    async fn update(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
        sqlx::query!(
            "UPDATE pages SET reference=$1, title=$2, content=$3, date_created=$4, date_last_updated=$5, description=$6 WHERE id=$7",
            self.reference,
            self.title,
            self.content,
            self.date_created,
            self.date_last_updated,
            self.description,
            self.id
        )
        .execute(pool)
        .await?;

        Ok(())
    }

    async fn delete(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
        let id = &self.id;
        crate::psql_delete!(id, pool, "pages")
    }
}

pub async fn load_pages(pool: &Pool<Postgres>) -> Result<(), Box<dyn Error>> {
    let paths = fs::read_dir("pages/").unwrap();
    for path_entry in paths {
        let path: PathBuf = path_entry.unwrap().path();
        let metadata = metadata(path.clone()).unwrap();
        if metadata.is_file() {
            let mut page: Page = Page::from_file(path);
            match Page::read_by_reference(pool, &page.reference).await {
                Ok(proj) => {
                    page.id = proj.id;
                    page.update(pool).await?
                }
                Err(_) => page.insert(pool).await?,
            }
        }
    }
    Ok(())
}
