use std::fmt::Display;

use email_address::EmailAddress;
use rusqlite::{
    Row, ToSql,
    types::{FromSql, ToSqlOutput},
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{database::db::DbError, sql_uuid_newtype, types::password::Password};

#[derive(Deserialize, Clone, Debug, Serialize)]
pub(crate) struct Mail(pub EmailAddress);

impl ToSql for Mail {
    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
        let s = self.0.email();
        Ok(ToSqlOutput::from(s))
    }
}

impl FromSql for Mail {
    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
        let s: String = FromSql::column_result(value)?;
        let email = EmailAddress::new_unchecked(s);
        let email = Mail(email);

        Ok(email)
    }
}

impl Display for Mail {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0.email())
    }
}

#[derive(Deserialize, Clone, Debug)]
pub struct RegisterUser {
    pub(crate) email: Mail,
    pub(crate) password: Password,
}

#[derive(Deserialize, Clone, Debug)]
pub struct LoginUser {
    pub(crate) email: Mail,
    pub(crate) password: Password,
}

#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
pub struct UserStatusId(Uuid);
sql_uuid_newtype!(UserStatusId);

#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
pub struct UserId(Uuid);
sql_uuid_newtype!(UserId);

#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
pub struct VerificationId(Uuid);
sql_uuid_newtype!(VerificationId);

#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
pub struct RoleId(Uuid);
sql_uuid_newtype!(RoleId);

#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
pub struct SessionId(Uuid);
sql_uuid_newtype!(SessionId);

#[derive(Debug, Clone, Serialize)]
pub struct User {
    pub(crate) user_id: UserId,
    pub(crate) email: Mail,
    pub(crate) user_state: UserState,
    pub(crate) role: RoleValue,
}

#[derive(Debug, Clone, Serialize)]
pub struct Role {
    pub(crate) role_id: RoleId,
    pub(crate) role: RoleValue,
}

#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub enum RoleValue {
    None,
    Admin,
    SuperUser,
    User,
}

pub trait FromRow: Sized {
    fn try_from_row(row: &Row) -> Result<Self, rusqlite::Error>;
}

impl FromRow for User {
    fn try_from_row(row: &Row) -> Result<Self, rusqlite::Error> {
        Ok(Self {
            user_id: row.get("user_id")?,
            email: row.get("email")?,
            user_state: row.get("user_state")?,
            role: row.get("role")?,
        })
    }
}

impl FromSql for RoleValue {
    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
        let s: String = FromSql::column_result(value)?;
        match s.as_str() {
            "admin" => Ok(RoleValue::Admin),
            "superuser" => Ok(RoleValue::SuperUser),
            "user" => Ok(RoleValue::User),
            _ => Ok(RoleValue::None),
        }
    }
}

impl Display for RoleValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let role = match self {
            RoleValue::None => "none",
            RoleValue::Admin => "admin",
            RoleValue::SuperUser => "superuser",
            RoleValue::User => "user",
        };
        write!(f, "{}", role)
    }
}

impl ToSql for RoleValue {
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
        let s = self.to_string();
        Ok(ToSqlOutput::from(s))
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum UserState {
    Pending,
    Verified,
}

impl FromSql for UserState {
    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
        let s: String = FromSql::column_result(value)?;
        match s.as_str() {
            "pending" => Ok(UserState::Pending),
            "verified" => Ok(UserState::Verified),
            _ => Err(rusqlite::types::FromSqlError::Other(Box::new(
                DbError::Generic("Not a valid user_state".to_owned()),
            ))),
        }
    }
}
