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 chrono::NaiveDate;

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Article {
    pub id: i32,
    pub reference: String,
    pub title: String,
    pub content: String,
    pub previous: Option<String>,
    pub next: Option<String>,
    pub date: NaiveDate,
    pub description: Option<String>,
}

impl Article {
    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 previous: Option<String> = None;
        let mut next: Option<String> = None;
        let mut date: Option<NaiveDate> = 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("previous: ") {
                previous = Some(line.clone().replace("previous: ", ""));
            } else if line.contains("next: ") {
                next = Some(line.clone().replace("next: ", ""));
            } else if line.contains("date: ") {
                date = Some(
                    NaiveDate::parse_from_str(&line.clone().replace("date: ", ""), "%Y-%m-%d").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");
        Article {
            id: 0,
            reference: reference.unwrap(),
            title: title.unwrap(),
            content: contents,
            previous,
            next,
            date: date.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 articles WHERE reference = $1",
            reference
        )
        .fetch_one(pool)
        .await?;

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

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

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

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

        Ok(())
    }

    async fn update(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
        sqlx::query!(
            "UPDATE articles SET reference=$1, title=$2, content=$3, previous=$4, next=$5, date=$6, description=$7 WHERE id=$8",
            self.reference,
            self.title,
            self.content,
            self.previous,
            self.next,
            self.date,
            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, "articles")
    }
}

pub async fn load_articles(pool: &Pool<Postgres>) -> Result<(), Box<dyn Error>> {
    let paths = fs::read_dir("posts/").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 article: Article = Article::from_file(path);
            match Article::read_by_reference(pool, &article.reference).await {
                Ok(art) => {
                    article.id = art.id;
                    article.update(pool).await?
                }
                Err(_) => article.insert(pool).await?,
            }
        }
    }
    Ok(())
}
