use chrono::NaiveDateTime;
#[cfg(feature = "diesel")]
use diesel::{
    AsExpression, FromSqlRow, Queryable, deserialize::FromSql, serialize::Output, serialize::ToSql,
    sql_types::Text, sqlite::Sqlite, sqlite::SqliteValue,
};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt;
use std::fmt::Formatter;

#[derive(Debug, Serialize, Deserialize)]
pub struct RebuildReport {
    pub queue_id: i32,
    pub built_at: NaiveDateTime,
    pub build_log: Vec<u8>,
    pub status: BuildStatus,
    pub artifacts: Vec<RebuildArtifactReport>,
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, clap::ValueEnum)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, AsExpression))]
#[cfg_attr(feature = "diesel", diesel(sql_type = Text))]
#[cfg_attr(feature = "diesel", diesel(check_for_backend(diesel::sqlite::Sqlite)))]
pub enum BuildStatus {
    #[serde(rename = "GOOD")]
    #[clap(name = "GOOD")]
    Good,

    #[serde(rename = "BAD")]
    #[clap(name = "BAD")]
    Bad,

    #[serde(rename = "FAIL")]
    #[clap(name = "FAIL")]
    Fail,

    #[serde(rename = "UNKWN")]
    #[clap(name = "UNKWN")]
    Unknown,
}

impl BuildStatus {
    pub fn as_str(&self) -> &str {
        match self {
            BuildStatus::Good => "GOOD",
            BuildStatus::Bad => "BAD",
            BuildStatus::Fail => "FAIL",
            BuildStatus::Unknown => "UNKWN",
        }
    }
}

#[derive(Debug, Clone)]
pub struct BuildStatusParseError {
    value: String,
}

impl fmt::Display for BuildStatusParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let value = &self.value;
        write!(f, "could not parse \"{value}\" as a build status")
    }
}

impl Error for BuildStatusParseError {}

impl TryFrom<&str> for BuildStatus {
    type Error = BuildStatusParseError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "GOOD" => Ok(BuildStatus::Good),
            "BAD" => Ok(BuildStatus::Bad),
            "FAIL" => Ok(BuildStatus::Fail),
            "UNKWN" => Ok(BuildStatus::Unknown),
            _ => Err(BuildStatusParseError {
                value: value.to_string(),
            }),
        }
    }
}

#[cfg(feature = "diesel")]
impl FromSql<Text, Sqlite> for BuildStatus {
    fn from_sql(bytes: SqliteValue) -> diesel::deserialize::Result<Self> {
        let t = <String as FromSql<Text, Sqlite>>::from_sql(bytes)?;
        Ok(t.as_str().try_into()?)
    }
}

#[cfg(feature = "diesel")]
impl ToSql<Text, Sqlite> for BuildStatus {
    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result {
        out.set_value(self.as_str());
        Ok(diesel::serialize::IsNull::No)
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RebuildArtifactReport {
    pub name: String,
    pub diffoscope: Option<Vec<u8>>,
    pub attestation: Option<Vec<u8>>,
    pub status: ArtifactStatus,
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, clap::ValueEnum)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, AsExpression))]
#[cfg_attr(feature = "diesel", diesel(sql_type = Text))]
#[cfg_attr(feature = "diesel", diesel(check_for_backend(diesel::sqlite::Sqlite)))]
pub enum ArtifactStatus {
    #[serde(rename = "GOOD")]
    #[clap(name = "GOOD")]
    Good,

    #[serde(rename = "BAD")]
    #[clap(name = "BAD")]
    Bad,

    #[serde(rename = "UNKWN")]
    #[clap(name = "UNKWN")]
    Unknown,
}

impl ArtifactStatus {
    pub fn as_str(&self) -> &str {
        match self {
            ArtifactStatus::Good => "GOOD",
            ArtifactStatus::Bad => "BAD",
            ArtifactStatus::Unknown => "UNKWN",
        }
    }
}

#[derive(Debug, Clone)]
pub struct ArtifactStatusParseError {
    value: String,
}

impl fmt::Display for ArtifactStatusParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let value = &self.value;
        write!(f, "could not parse \"{value}\" as an artifact status")
    }
}

impl Error for ArtifactStatusParseError {}

impl TryFrom<&str> for ArtifactStatus {
    type Error = ArtifactStatusParseError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "GOOD" => Ok(ArtifactStatus::Good),
            "BAD" => Ok(ArtifactStatus::Bad),
            "UNKWN" => Ok(ArtifactStatus::Unknown),
            _ => Err(ArtifactStatusParseError {
                value: value.to_string(),
            }),
        }
    }
}

#[cfg(feature = "diesel")]
impl FromSql<Text, Sqlite> for ArtifactStatus {
    fn from_sql(bytes: SqliteValue) -> diesel::deserialize::Result<Self> {
        let t = <String as FromSql<Text, Sqlite>>::from_sql(bytes)?;
        Ok(t.as_str().try_into()?)
    }
}

#[cfg(feature = "diesel")]
impl ToSql<Text, Sqlite> for ArtifactStatus {
    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result {
        out.set_value(self.as_str());
        Ok(diesel::serialize::IsNull::No)
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "diesel", derive(Queryable))]
#[cfg_attr(feature = "diesel", diesel(check_for_backend(diesel::sqlite::Sqlite)))]
pub struct Rebuild {
    pub id: i32,
    pub name: String,
    pub version: String,
    pub distribution: String,
    pub release: Option<String>,
    pub architecture: String,
    pub backend: String,
    pub retries: i32,
    pub started_at: Option<NaiveDateTime>,
    pub built_at: Option<NaiveDateTime>,
    pub status: Option<BuildStatus>,
}

#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "diesel", derive(Queryable))]
#[cfg_attr(feature = "diesel", diesel(check_for_backend(diesel::sqlite::Sqlite)))]
pub struct RebuildArtifact {
    pub id: i32,
    pub name: String,
    pub has_diffoscope: bool,
    pub has_attestation: bool,
    pub status: Option<ArtifactStatus>,
}
