use crate::models::{ArticleID, FeedID, Url};
use crate::schema::articles;
use chrono::{DateTime, Utc};
use diesel::backend::Backend;
use diesel::deserialize;
use diesel::deserialize::FromSql;
use diesel::serialize::{self, IsNull};
use diesel::serialize::{Output, ToSql};
use diesel::sql_types::Integer;
use diesel::sqlite::Sqlite;
use serde::{Deserialize, Serialize};
use std::io::{Error, ErrorKind, Write};
use std::path::PathBuf;

#[derive(Identifiable, Insertable, Queryable, Clone, Eq, PartialEq, Debug)]
#[diesel(primary_key(article_id))]
#[diesel(belongs_to(Feed))]
#[diesel(treat_none_as_default_value = false)]
#[diesel(table_name = articles)]
#[diesel(check_for_backend(SQLite))]
pub struct Article {
    pub article_id: ArticleID,
    pub title: Option<String>,
    pub author: Option<String>,
    pub feed_id: FeedID,
    pub url: Option<Url>,
    pub date: DateTime<Utc>,
    pub synced: DateTime<Utc>,
    pub summary: Option<String>,
    pub direction: Option<Direction>,
    pub unread: Read,
    pub marked: Marked,
    pub thumbnail_url: Option<String>,
    pub updated: Option<DateTime<Utc>>,
}

//------------------------------------------------------------------

#[derive(Identifiable, Insertable, Queryable, Clone, Eq, PartialEq, Debug)]
#[diesel(primary_key(article_id))]
#[diesel(belongs_to(Feed))]
#[diesel(treat_none_as_default_value = false)]
#[diesel(table_name = articles)]
#[diesel(check_for_backend(SQLite))]
pub struct FatArticle {
    pub article_id: ArticleID,
    pub title: Option<String>,
    pub author: Option<String>,
    pub feed_id: FeedID,
    pub url: Option<Url>,
    pub date: DateTime<Utc>,
    pub synced: DateTime<Utc>,
    pub html: Option<String>,
    pub summary: Option<String>,
    pub direction: Option<Direction>,
    pub unread: Read,
    pub marked: Marked,
    pub scraped_content: Option<String>,
    pub plain_text: Option<String>,
    pub thumbnail_url: Option<String>,
    pub updated: Option<DateTime<Utc>>,
}

impl FatArticle {
    pub fn save_html(&self, path: &PathBuf) -> Result<(), Error> {
        if let Some(ref html) = self.html
            && let Ok(()) = std::fs::create_dir_all(path)
        {
            let mut file_name = match self.title.clone() {
                Some(file_name) => file_name,
                None => "Unknown Title".to_owned(),
            };
            file_name.push_str(".html");
            let path = path.join(file_name);
            let mut html_file = std::fs::File::create(path)?;
            html_file.write_all(html.as_bytes())?;
        }

        Err(Error::new(ErrorKind::NotFound, "No HTML for article"))
    }
}

//------------------------------------------------------------------

#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
#[diesel(sql_type = Integer)]
pub enum Direction {
    #[default]
    LeftToRight,
    RightToLeft,
}

impl Direction {
    pub fn to_int(self) -> i32 {
        self as i32
    }

    pub fn from_int(int: i32) -> Self {
        match int {
            0 => Direction::LeftToRight,
            1 => Direction::RightToLeft,

            _ => Direction::LeftToRight,
        }
    }
}

impl FromSql<Integer, Sqlite> for Direction {
    fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
        let int = i32::from_sql(bytes)?;
        Ok(Direction::from_int(int))
    }
}

impl ToSql<Integer, Sqlite> for Direction {
    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
        out.set_value(*self as i32);
        Ok(IsNull::No)
    }
}

//------------------------------------------------------------------

#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
#[diesel(sql_type = Integer)]
pub enum Read {
    Read,
    #[default]
    Unread,
}

impl Read {
    pub fn to_int(self) -> i32 {
        self as i32
    }

    pub fn from_int(int: i32) -> Self {
        match int {
            0 => Read::Read,
            1 => Read::Unread,

            _ => Read::Read,
        }
    }

    pub fn invert(&self) -> Read {
        match self {
            Read::Read => Read::Unread,
            Read::Unread => Read::Read,
        }
    }
}

impl FromSql<Integer, Sqlite> for Read {
    fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
        let int = i32::from_sql(bytes)?;
        Ok(Read::from_int(int))
    }
}

impl ToSql<Integer, Sqlite> for Read {
    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
        out.set_value(*self as i32);
        Ok(IsNull::No)
    }
}

//------------------------------------------------------------------

#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
#[diesel(sql_type = Integer)]
pub enum Marked {
    Marked,
    #[default]
    Unmarked,
}

impl Marked {
    pub fn to_int(self) -> i32 {
        self as i32
    }

    pub fn from_int(int: i32) -> Self {
        match int {
            0 => Marked::Marked,
            1 => Marked::Unmarked,

            _ => Marked::Unmarked,
        }
    }

    pub fn invert(&self) -> Marked {
        match self {
            Marked::Marked => Marked::Unmarked,
            Marked::Unmarked => Marked::Marked,
        }
    }
}

impl FromSql<Integer, Sqlite> for Marked {
    fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
        let int = i32::from_sql(bytes)?;
        Ok(Marked::from_int(int))
    }
}

impl ToSql<Integer, Sqlite> for Marked {
    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
        out.set_value(*self as i32);
        Ok(IsNull::No)
    }
}

//------------------------------------------------------------------

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum ArticleOrder {
    #[default]
    NewestFirst,
    OldestFirst,
}

impl ArticleOrder {
    pub fn invert(&self) -> ArticleOrder {
        match self {
            ArticleOrder::NewestFirst => ArticleOrder::OldestFirst,
            ArticleOrder::OldestFirst => ArticleOrder::NewestFirst,
        }
    }
}

//------------------------------------------------------------------

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum OrderBy {
    #[default]
    Updated,
    Published,
}
