use crate::app_config::AppConfig;
use crate::constants;
use crate::schema::{
    accommodations_list, accommodations_reservations, accommodations_reservations_cals_urls,
    couples, families, members, memberships, photos, users,
};
use crate::utils::crypt_utils::sha256;
use crate::utils::time_utils::time;
use diesel::prelude::*;

/// User ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct UserID(pub i32);

#[derive(Queryable, Debug, serde::Serialize)]
pub struct User {
    id: i32,
    pub name: String,
    pub email: String,
    #[serde(skip_serializing)]
    pub password: Option<String>,
    pub time_create: i64,
    #[serde(skip_serializing)]
    pub reset_password_token: Option<String>,
    #[serde(skip_serializing)]
    pub time_gen_reset_token: i64,
    #[serde(skip_serializing)]
    pub delete_account_token: Option<String>,
    #[serde(skip_serializing)]
    pub time_gen_delete_account_token: i64,
    pub time_activate: i64,
    pub active: bool,
    pub admin: bool,
}

impl User {
    pub fn id(&self) -> UserID {
        UserID(self.id)
    }

    pub fn check_password(&self, password: &str) -> bool {
        self.password
            .as_deref()
            .map(|hash| {
                bcrypt::verify(password, hash).unwrap_or_else(|e| {
                    log::error!("Failed to validate password! {e}");
                    false
                })
            })
            .unwrap_or(false)
    }
}

#[derive(Insertable)]
#[diesel(table_name = users)]
pub struct NewUser<'a> {
    pub name: &'a str,
    pub email: &'a str,
    pub time_create: i64,
}

/// Family ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct FamilyID(pub i32);

#[derive(Queryable, Debug, serde::Serialize)]
pub struct Family {
    id: i32,
    pub time_create: i64,
    pub name: String,
    pub invitation_code: String,
    pub disable_couple_photos: bool,
    pub enable_genealogy: bool,
    pub enable_accommodations: bool,
}

impl Family {
    pub fn id(&self) -> FamilyID {
        FamilyID(self.id)
    }
}

#[derive(Insertable)]
#[diesel(table_name = families)]
pub struct NewFamily<'a> {
    pub name: &'a str,
    pub invitation_code: String,
    pub time_create: i64,
}

#[derive(Queryable, Debug, serde::Serialize)]
pub struct Membership {
    user_id: i32,
    family_id: i32,
    pub time_create: i64,
    pub is_admin: bool,
}

impl Membership {
    pub fn user_id(&self) -> UserID {
        UserID(self.user_id)
    }

    pub fn family_id(&self) -> FamilyID {
        FamilyID(self.family_id)
    }
}

#[derive(Insertable)]
#[diesel(table_name = memberships)]
pub struct NewMembership {
    pub user_id: i32,
    pub family_id: i32,
    pub time_create: i64,
    pub is_admin: bool,
}

#[derive(Queryable, Debug, serde::Serialize)]
pub struct FamilyMembership {
    user_id: i32,
    family_id: i32,
    name: String,
    time_create: i64,
    pub is_admin: bool,
    invitation_code: String,
    pub count_members: i64,
    pub count_admins: i64,
}

/// Photo ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct PhotoID(pub i32);

impl PhotoID {
    pub fn to_signed_hash(&self) -> String {
        let secret = AppConfig::get().secret();
        format!(
            "{}-{}",
            self.0,
            sha256(format!("{secret}{}{secret}", self.0).as_bytes())
        )
    }

    pub fn from_signed_hash(hash: &str) -> Option<Self> {
        let (id, _) = hash.split_once('-')?;
        let id = Self(id.parse().ok()?);

        if id.to_signed_hash() != hash {
            return None;
        }

        Some(id)
    }
}

#[derive(Queryable, Debug, serde::Serialize)]
pub struct Photo {
    id: i32,
    pub file_id: String,
    pub time_create: i64,
    pub mime_type: String,
    pub sha512: String,
    pub file_size: i32,
    pub thumb_sha512: String,
}

impl Photo {
    pub fn id(&self) -> PhotoID {
        PhotoID(self.id)
    }

    pub fn photo_path(&self) -> String {
        format!("photo/{}", self.file_id)
    }

    pub fn thumbnail_path(&self) -> String {
        format!("thumbnail/{}", self.file_id)
    }

    pub fn mime_extension(&self) -> Option<&str> {
        mime_guess::get_mime_extensions_str(&self.mime_type)
            .map(|e| e.first())
            .unwrap_or_default()
            .copied()
    }
}

#[derive(Insertable)]
#[diesel(table_name = photos)]
pub struct NewPhoto {
    pub file_id: String,
    pub time_create: i64,
    pub mime_type: String,
    pub sha512: String,
    pub file_size: i32,
    pub thumb_sha512: String,
}

impl NewPhoto {
    pub fn photo_path(&self) -> String {
        format!("photo/{}", self.file_id)
    }

    pub fn thumbnail_path(&self) -> String {
        format!("thumbnail/{}", self.file_id)
    }
}

/// Member ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct MemberID(pub i32);

#[derive(serde::Serialize, serde::Deserialize, Copy, Clone)]
pub enum Sex {
    #[serde(rename = "M")]
    Male,
    #[serde(rename = "F")]
    Female,
}

impl Sex {
    pub fn parse_str(s: &str) -> Option<Self> {
        match s {
            "M" => Some(Sex::Male),
            "F" => Some(Sex::Female),
            _ => None,
        }
    }

    pub fn to_str(&self) -> &'static str {
        match self {
            Sex::Male => "M",
            Sex::Female => "F",
        }
    }
}

#[derive(Queryable, Debug, serde::Serialize, Clone)]
pub struct Member {
    id: i32,
    family_id: i32,
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub birth_last_name: Option<String>,
    photo_id: Option<i32>,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub address: Option<String>,
    pub city: Option<String>,
    pub postal_code: Option<String>,
    pub country: Option<String>,
    sex: Option<String>,
    time_create: i64,
    pub time_update: i64,
    mother: Option<i32>,
    father: Option<i32>,
    pub birth_year: Option<i16>,
    pub birth_month: Option<i16>,
    pub birth_day: Option<i16>,
    pub dead: bool,
    pub death_year: Option<i16>,
    pub death_month: Option<i16>,
    pub death_day: Option<i16>,
    pub note: Option<String>,
}

impl Member {
    pub fn id(&self) -> MemberID {
        MemberID(self.id)
    }

    pub fn family_id(&self) -> FamilyID {
        FamilyID(self.family_id)
    }

    pub fn set_photo_id(&mut self, p: Option<PhotoID>) {
        self.photo_id = p.map(|p| p.0)
    }

    pub fn photo_id(&self) -> Option<PhotoID> {
        self.photo_id.map(PhotoID)
    }

    pub fn sex(&self) -> Option<Sex> {
        self.sex.as_deref().map(Sex::parse_str).unwrap_or_default()
    }

    pub fn set_sex(&mut self, s: Option<Sex>) {
        self.sex = s.map(|s| s.to_str().to_string())
    }

    pub fn mother(&self) -> Option<MemberID> {
        self.mother.map(MemberID)
    }

    pub fn set_mother(&mut self, p: Option<MemberID>) {
        self.mother = p.map(|p| p.0);
    }

    pub fn father(&self) -> Option<MemberID> {
        self.father.map(MemberID)
    }

    pub fn set_father(&mut self, p: Option<MemberID>) {
        self.father = p.map(|p| p.0);
    }
}

#[derive(Insertable)]
#[diesel(table_name = members)]
pub struct NewMember {
    pub family_id: i32,
    pub time_create: i64,
    pub time_update: i64,
}

/// Couple ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct CoupleID(pub i32);

#[derive(serde::Serialize, serde::Deserialize, Copy, Clone)]
pub enum CoupleState {
    #[serde(rename = "N")]
    None,
    #[serde(rename = "E")]
    Engaged,
    #[serde(rename = "M")]
    Married,
    #[serde(rename = "D")]
    Divorced,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct CoupleStateDesc {
    code: &'static str,
    fr: &'static str,
    en: &'static str,
}

impl CoupleState {
    pub fn as_str(&self) -> &str {
        match self {
            CoupleState::None => "N",
            CoupleState::Engaged => "E",
            CoupleState::Married => "M",
            CoupleState::Divorced => "D",
        }
    }

    pub fn parse_str(s: &str) -> Option<Self> {
        serde_json::from_str(&format!("\"{s}\"")).ok()
    }

    pub fn states_list() -> Vec<CoupleStateDesc> {
        vec![
            CoupleStateDesc {
                code: "N",
                fr: "Aucun",
                en: "None",
            },
            CoupleStateDesc {
                code: "E",
                fr: "Fiancés",
                en: "Engaged",
            },
            CoupleStateDesc {
                code: "M",
                fr: "Mariés",
                en: "Married",
            },
            CoupleStateDesc {
                code: "D",
                fr: "Divorcés",
                en: "Divorced",
            },
        ]
    }
}

#[derive(Queryable, Debug, serde::Serialize)]
pub struct Couple {
    id: i32,
    family_id: i32,
    wife: Option<i32>,
    husband: Option<i32>,
    state: Option<String>,
    photo_id: Option<i32>,
    time_create: i64,
    pub time_update: i64,
    pub wedding_year: Option<i16>,
    pub wedding_month: Option<i16>,
    pub wedding_day: Option<i16>,
    pub divorce_year: Option<i16>,
    pub divorce_month: Option<i16>,
    pub divorce_day: Option<i16>,
}

impl Couple {
    pub fn id(&self) -> CoupleID {
        CoupleID(self.id)
    }

    pub fn family_id(&self) -> FamilyID {
        FamilyID(self.family_id)
    }

    pub fn state(&self) -> Option<CoupleState> {
        self.state
            .as_deref()
            .map(CoupleState::parse_str)
            .unwrap_or_default()
    }

    pub fn set_state(&mut self, s: Option<CoupleState>) {
        self.state = s.map(|s| s.as_str().to_string())
    }

    pub fn set_wife(&mut self, s: Option<MemberID>) {
        self.wife = s.map(|s| s.0)
    }

    pub fn wife(&self) -> Option<MemberID> {
        self.wife.map(MemberID)
    }

    pub fn set_husband(&mut self, s: Option<MemberID>) {
        self.husband = s.map(|s| s.0)
    }

    pub fn husband(&self) -> Option<MemberID> {
        self.husband.map(MemberID)
    }

    pub fn set_photo_id(&mut self, p: Option<PhotoID>) {
        self.photo_id = p.map(|p| p.0);
    }

    pub fn photo_id(&self) -> Option<PhotoID> {
        self.photo_id.map(PhotoID)
    }
}

#[derive(Insertable)]
#[diesel(table_name = couples)]
pub struct NewCouple {
    pub family_id: i32,
    pub time_create: i64,
    pub time_update: i64,
}

/// Accommodation ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct AccommodationID(pub i32);

#[derive(Queryable, Debug, serde::Serialize)]
pub struct Accommodation {
    id: i32,
    family_id: i32,
    time_create: i64,
    pub time_update: i64,
    pub name: String,
    pub need_validation: bool,
    pub description: Option<String>,
    pub color: Option<String>,
    pub open_to_reservations: bool,
}

impl Accommodation {
    pub fn id(&self) -> AccommodationID {
        AccommodationID(self.id)
    }

    pub fn family_id(&self) -> FamilyID {
        FamilyID(self.family_id)
    }
}

#[derive(Insertable)]
#[diesel(table_name = accommodations_list)]
pub struct NewAccommodation {
    pub family_id: i32,
    pub name: String,
    pub time_create: i64,
    pub time_update: i64,
}

/// Accommodation reservation ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct AccommodationReservationID(pub i32);

pub enum ReservationStatus {
    Pending,
    Accepted,
    Rejected,
}

#[derive(Queryable, Debug, serde::Serialize)]
pub struct AccommodationReservation {
    id: i32,
    family_id: i32,
    accommodation_id: i32,
    user_id: i32,
    time_create: i64,
    pub time_update: i64,
    pub reservation_start: i64,
    pub reservation_end: i64,
    pub validated: Option<bool>,
}

impl AccommodationReservation {
    pub fn id(&self) -> AccommodationReservationID {
        AccommodationReservationID(self.id)
    }

    pub fn accommodation_id(&self) -> AccommodationID {
        AccommodationID(self.accommodation_id)
    }

    pub fn family_id(&self) -> FamilyID {
        FamilyID(self.family_id)
    }

    pub fn user_id(&self) -> UserID {
        UserID(self.user_id)
    }

    pub fn status(&self) -> ReservationStatus {
        match self.validated {
            None => ReservationStatus::Pending,
            Some(true) => ReservationStatus::Accepted,
            Some(false) => ReservationStatus::Rejected,
        }
    }
}

#[derive(Insertable)]
#[diesel(table_name = accommodations_reservations)]
pub struct NewAccommodationReservation {
    pub family_id: i32,
    pub accommodation_id: i32,
    pub user_id: i32,
    pub time_create: i64,
    pub time_update: i64,
    pub reservation_start: i64,
    pub reservation_end: i64,
}

/// Accommodation reservation calendar ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct AccommodationReservationCalendarID(pub i32);

#[derive(Queryable, Debug, serde::Serialize)]
pub struct AccommodationReservationCalendar {
    id: i32,
    family_id: i32,
    accommodation_id: Option<i32>,
    user_id: i32,
    name: String,
    token: String,
    pub time_create: i64,
    pub time_used: i64,
}

impl AccommodationReservationCalendar {
    pub fn id(&self) -> AccommodationReservationCalendarID {
        AccommodationReservationCalendarID(self.id)
    }

    pub fn accommodation_id(&self) -> Option<AccommodationID> {
        self.accommodation_id.map(AccommodationID)
    }

    pub fn family_id(&self) -> FamilyID {
        FamilyID(self.family_id)
    }

    pub fn user_id(&self) -> UserID {
        UserID(self.user_id)
    }

    pub fn should_update_last_used(&self) -> bool {
        (self.time_used
            + constants::ACCOMMODATIONS_RESERVATIONS_CAL_URL_TIME_USED_UPDATE_MIN_INTERVAL.as_secs()
                as i64)
            < time() as i64
    }
}

#[derive(Insertable)]
#[diesel(table_name = accommodations_reservations_cals_urls)]
pub struct NewAccommodationReservationCalendar {
    pub family_id: i32,
    pub accommodation_id: Option<i32>,
    pub user_id: i32,
    pub name: String,
    pub token: String,
    pub time_create: i64,
    pub time_used: i64,
}
