#![allow(unused_variables)]

use std::fmt::{Display, Formatter};
use std::str::FromStr;
use chrono::{Local, NaiveDate, NaiveTime, Weekday};
use lazy_static::lazy_static;
use regex::Regex;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use validator::{Validate, ValidationError};
use crate::error::AppError;

#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum ApiResponse<T> {
    Data(T),
    Error(String)
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct Company {
    id: i32,
    #[validate(length(min = 1,message = "Name cannot be empty"))]
    pub name: String,
    #[validate(length(min = 1,message = "Street cannot be empty"))]
    pub street: String,
    #[validate(length(min = 1,message = "House number cannot be empty"))]
    pub house_number: String,
    pub zip_code: String,
    pub city: String,
}

impl Company {
    pub fn id(&self) -> i32 {
        self.id
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct User {
    id: i32,
    pub login: String,
    pub password: String,
    pub full_name: Option<String>,
    #[validate(email(message = "Enter valid email address"))]
    pub email: Option<String>,
    pub admin: bool,
    pub get_emails: bool,
}

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

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
pub struct UserProfile {
    #[validate(length(min = 1,message = "Username cannot be empty"))]
    login: String,
    #[validate(must_match(other = "password_ver", message = "Passwords doesn't match"))]
    password: Option<String>,
    password_ver: Option<String>,
    full_name: String,
    #[validate(email(message = "Enter valid email address"))]
    email: String,
    get_emails: Option<String>,
    admin: Option<String>
}

impl UserProfile {

    pub fn login(&self) -> &str  {
        &self.login
    }

    pub fn full_name(&self) -> &str {
        &self.full_name
    }

    pub fn email(&self) -> &str {
        &self.email
    }

    pub fn get_emails(&self) -> bool {
        self.get_emails.is_some()
    }
    pub fn admin(&self) -> bool {
        self.admin.is_some()
    }
    pub fn password(&self) -> &Option<String> {
        &self.password
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
pub struct PwdChange {
    login: String,
    old_password: String,
    #[validate(length(min = 1, message = "Enter new password"),
    must_match(other = "password_ver", message = "Passwords doesn't match"))]
    password: String,
    password_ver: String
}

impl PwdChange {
    pub fn login(&self) -> &str {
        &self.login
    }
    pub fn old_password(&self) -> &str {
        &self.old_password
    }
    pub fn password(&self) -> &str {
        &self.password
    }
    pub fn password_ver(&self) -> &str {
        &self.password_ver
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct OpeningHour {
    id: i32,
    pub day: i32,
    pub from: NaiveTime,
    pub to: NaiveTime,
    pub discount: Option<i32>
}

impl OpeningHour {
    pub fn id(&self) -> i32 {
        self.id
    }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DayHours(pub Vec<DayHour>);

impl DayHours {

    pub fn try_new(hours: &str) -> Result<Self, AppError> {
        if hours.is_empty() {
            return Ok(Self(Vec::new()))
        }

        let times = hours.split(",")
            .map(|h| h.trim())
            .map(|h| DayHour::try_from(h))
            .collect::<Vec<_>>();

        if times.contains(&Err(AppError::HourParseError)) {
            return Err(AppError::HourParseError)
        }

        Ok(Self(times.into_iter().map(|h| h.unwrap()).collect()))
    }

    pub fn hours(&self) -> &Vec<DayHour> {
        &self.0
    }
}

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

        let str = self.0.iter().map(|h| {
            let discount = if let Some(d) = h.discount() {
                format!(" ({})", d).to_string()
            } else { "".to_string() };
            format!("{} - {}{}", h.from().format("%H:%M"), h.to().format("%H:%M"), discount).to_string()
        })
            .collect::<Vec<String>>()
            .join(", ");
        write!(f, "{}", str)
    }
}

#[derive(Eq, PartialEq, Serialize, Deserialize, Clone, Debug)]
pub struct DayHour {
    from: NaiveTime,
    to: NaiveTime,
    discount: Option<i32>
}

impl DayHour {
    pub fn new(from: NaiveTime, to: NaiveTime, discount: Option<i32>) -> Self {
        Self { from, to, discount }
    }

    pub fn from(&self) -> NaiveTime {
        self.from
    }
    pub fn to(&self) -> NaiveTime {
        self.to
    }
    pub fn discount(&self) -> Option<i32> {
        self.discount
    }
}

impl TryFrom<String> for DayHour {
    type Error = AppError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        value.as_str().try_into()
    }
}

impl TryFrom<&str> for DayHour {
    type Error = AppError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        if value.is_empty() {
            return Err(AppError::HourParseError)
        }

        let times = value.split("-")
            .map(|t| t.trim()).collect::<Vec<_>>();

        if times.len() != 2 {
            return Err(AppError::HourParseError)
        }

        let from = NaiveTime::parse_from_str(times.get(0).unwrap(), "%H:%M");
        let to = NaiveTime::parse_from_str(times.get(1).unwrap(), "%H:%M");

        if from.is_err() || to.is_err() {
            return Err(AppError::HourParseError)
        }

        Ok(DayHour {
            from: from.unwrap(),
            to: to.unwrap(),
            discount: None
        })
    }
}

fn validate_hours(value: &WeekHours) -> Result<(), ValidationError> {
    if value.hours().is_empty() {
        return Ok(())
    }
    if let Ok(h) = DayHours::try_new(value.hours()) {
        for hr in h.hours() {
            if hr.from() >= hr.to() { return Err(ValidationError::new("TO_BEFORE_FROM")) }
        }
        Ok(())
    } else {
        Ok(())
    }
}

lazy_static! {
    static ref RE_HOURS: Regex = Regex::new(r"^$|(^\d{2}:\d{2} ?- ?\d{2}:\d{2} ?(\(\d+\))?,? ?)+").unwrap();
}

#[derive(Clone, Serialize, Deserialize, Debug, Validate)]
#[validate(schema(function = "validate_hours", message = "Time 'to' must be after time 'from'"))]
pub struct WeekHours {
    day: Weekday,
    #[validate(regex(path = "RE_HOURS", message = "Hours must be in HH:MM - HH:MM format"))]
    hours: String
}

impl Default for WeekHours {
    fn default() -> Self {
        Self {
            day: Weekday::Mon,
            hours: String::default()
        }
    }
}

impl WeekHours {
    pub fn new(day: Weekday, hours: Vec<DayHour>) -> Self {
        Self {
            day,
            hours: DayHours(hours).to_string()
        }
    }
    pub fn day(&self) -> Weekday {
        self.day
    }
    pub fn hours(&self) -> &str {
        &self.hours
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
#[cfg_attr(feature = "ssr", sqlx(type_name = "slot_type"))]
pub enum SlotType {
    Quarter,
    Half,
    #[default]
    Hour,
    Day
}

impl Display for SlotType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", match self {
            SlotType::Quarter => {"Quarter an hour"}
            SlotType::Half => {"Half an hour"}
            SlotType::Hour => {"Hour"}
            SlotType::Day => {"Day"}
        })
    }
}

fn def_true() -> bool {
    true
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct ResProperty {
    #[cfg_attr(feature = "ssr", sqlx(default))]
    id: i32,
    #[validate(length(min = 1,message = "Name cannot be empty"))]
    pub name: String,
    pub description: String,
    #[cfg_attr(feature = "ssr", sqlx(default))]
    pub price: Decimal,
    #[cfg_attr(feature = "ssr", sqlx(default))]
    pub slot: SlotType,
    #[serde(default = "def_true")]
    #[cfg_attr(feature = "ssr", sqlx(default))]
    pub allow_multi: bool,
    #[serde(default = "def_true")]
    #[cfg_attr(feature = "ssr", sqlx(default))]
    pub active: bool
}

impl ResProperty {
    pub fn id(&self) -> i32 {
        self.id
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
pub struct PublicFormData {
    pub property: ResProperty,
    pub hours: Vec<DayHour>,
    pub reservations: Vec<Reservation>
}

fn empty_slots() -> Vec<String> {
    vec![]
}

fn validate_date(date: &NaiveDate) -> Result<(), ValidationError> {
    if date < &Local::now().date_naive() {
        Err(ValidationError::new("date_in_past"))
    } else {
        Ok(())
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, Validate)]
pub struct CrReservation {
    #[validate(custom(function = "validate_date", message = "Date can't be in past"))]
    date: NaiveDate,
    #[validate(length(min = 1,message = "Select at last one time slot"))]
    #[serde(default = "empty_slots")]
    slots: Vec<String>,
    #[validate(length(min = 1,message = "Enter your full name"))]
    full_name: String,
    #[validate(email(message = "Enter valid email address"))]
    email: String,
    #[validate(length(min = 1,message = "Enter your phone number"))]
    phone: String,
    note: String,
    remember: bool
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct TmCheck {
    pub from: NaiveTime,
    pub to: NaiveTime
}

impl FromStr for TmCheck {
    type Err = AppError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let times = s.split("-").collect::<Vec<_>>();
        if times.len() != 2 {
            return Err(AppError::HourParseError);
        }

        Ok(TmCheck{
            from: NaiveTime::from_str(times.get(0).unwrap_or(&""))?,
            to: NaiveTime::from_str(times.get(1).unwrap_or(&""))?
        })
    }
}

impl CrReservation {
    pub fn date(&self) -> NaiveDate {
        self.date
    }
    pub fn slots(&self) -> &Vec<String> {
        &self.slots
    }
    pub fn full_name(&self) -> &str {
        &self.full_name
    }
    pub fn email(&self) -> &str {
        &self.email
    }
    pub fn phone(&self) -> &str {
        &self.phone
    }
    pub fn note(&self) -> &str {
        &self.note
    }
    pub fn remember(&self) -> bool {
        self.remember
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct Customer {
    #[cfg_attr(feature = "ssr", sqlx(default))]
    id: i32,
    pub full_name: String,
    pub email: String,
    pub phone: String,
    #[cfg_attr(feature = "ssr", sqlx(default))]
    pub discount: i32
}

impl Customer {

    pub fn id(&self) -> i32 {
        self.id
    }

    pub fn new(id: i32, full_name: String, email: String, phone: String, discount: i32) -> Self {
        Self { id, full_name, email, phone, discount }
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
#[cfg_attr(feature = "ssr", sqlx(type_name = "reservation_state"))]
pub enum ReservationState {
    #[default]
    New,
    Approved,
    Canceled,
}

impl Display for ReservationState {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", match self {
            ReservationState::New => {"New"}
            ReservationState::Approved => {"Approved"}
            ReservationState::Canceled => {"Canceled"}
        })
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct Reservation {
    #[cfg_attr(feature = "ssr", sqlx(default))]
    id: i32,
    pub from: NaiveTime,
    pub to: NaiveTime,
    #[cfg_attr(feature = "ssr", sqlx(default))]
    pub property: i32,
    #[cfg_attr(feature = "ssr", sqlx(default))]
    pub summary: i32,
}

#[derive(Clone, Serialize, Deserialize, Debug, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct ResWithProperty {
    #[cfg_attr(feature = "ssr", sqlx(flatten))]
    pub reservation: Reservation,
    #[cfg_attr(feature = "ssr", sqlx(flatten))]
    pub property: ResProperty
}

pub struct Reservations(Vec<Reservation>);

// Transform slots to reservations
impl Reservations {

    pub fn new() -> Self {
        Reservations(vec![])
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub fn add_slot(&mut self, check: &TmCheck, property: i32) {
        let mut added = false;
        self.0.iter_mut().for_each(|r| {
            if r.property == property && r.to == check.from {
                r.to = check.to;
                added = true;
            }
        });

        if !added {
            self.0.push(Reservation {
                from: check.from,
                to: check.to,
                property,
                id: 0,
                summary: 0,
            });
        }
    }

    pub fn reservations(&self) -> &Vec<Reservation> {
        &self.0
    }
}

impl Reservation {
    pub fn new(from: NaiveTime, to: NaiveTime, property: i32) -> Self {
        Self { id: 0, from, to, property, summary: 0 }
    }

    pub fn id(&self) -> i32 {
        self.id
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct ReservationSum {
    id: i32,
    pub uuid: Uuid,
    pub date: NaiveDate,
    pub customer: i32,
    pub price: Decimal,
    pub state: ReservationState,
    pub date_create: NaiveDate,
    pub edited_by: Option<i32>,
    pub note: Option<String>,
}

impl ReservationSum {
    pub fn id(&self) -> i32 {
        self.id
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct ResSumWithItems {
    pub summary: ReservationSum,
    pub customer: Customer,
    pub reservations: Vec<ResWithProperty>
}

#[derive(Clone, Serialize, Deserialize, Debug, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct ResAllView {
    #[cfg_attr(feature = "ssr", sqlx(flatten))]
    pub reservation: ResWithProperty,
    #[cfg_attr(feature = "ssr", sqlx(flatten))]
    pub summary: ReservationSum,
    #[cfg_attr(feature = "ssr", sqlx(flatten))]
    pub customer: Customer,
}

#[derive(Clone, Serialize, Deserialize, Debug, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
#[cfg_attr(feature = "ssr", sqlx(type_name = "message_type"))]
pub enum MessageType {
    #[default]
    NewReservation,
    NewReservationCust,
    ReservationApp,
    ReservationCanceled,
}

impl Display for MessageType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", match self {
            MessageType::NewReservation => {"NewReservation"}
            MessageType::NewReservationCust => {"NewReservationCust"}
            MessageType::ReservationApp => {"ReservationApp"}
            MessageType::ReservationCanceled => {"ReservationCanceled"}
        })
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, Default, Validate)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct Message {
    id: i32,
    pub msg_type: MessageType,
    #[validate(length(min = 1,message = "Enter mail subject"))]
    pub subject: String,
    #[validate(length(min = 1,message = "Enter text"))]
    pub text: String,
}

impl Message {
    pub fn id(&self) -> i32 {
        self.id
    }
}

#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct ChartData {
    pub count: i64,
    pub period: f64
}

#[derive(Clone, Debug, Default, Serialize, Deserialize, Validate)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct Appearance {
    id: i32,
    pub banner: Option<String>,
    pub text: Option<String>,
    pub title: Option<String>
}

impl Appearance {
    pub fn id(&self) -> i32 {
        self.id
    }
}
