use std::collections::HashMap;
use cfg_if::cfg_if;
use chrono::Weekday;
use leptos::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, DayHour, WeekHours};
use crate::components::data_form::ForValidation;

cfg_if! { if #[cfg(feature = "ssr")] {
    use crate::error::AppError;

    pub async fn hours_for_day(day: Weekday) -> Result<Vec<DayHour>, AppError> {
        use crate::backend::get_pool;
        use crate::backend::data::OpeningHour;

        let pool = get_pool().await?;
        let hours = sqlx::query_as::<_, OpeningHour>("SELECT * FROM opening_hour WHERE day = $1")
            .bind(day.num_days_from_monday() as i32)
            .fetch_all(&pool)
            .await?;

        Ok(hours.into_iter().map(|h| { DayHour::new(h.from, h.to, h.discount)}).collect())
    }
}}

#[server]
pub async fn get_hours() -> Result<HashMap<Weekday, Vec<DayHour>>, ServerFnError> {
    use crate::backend::get_pool;
    use crate::backend::data::OpeningHour;

    let pool = get_pool().await?;
    let hours = sqlx::query_as::<_, OpeningHour>("SELECT * FROM opening_hour").fetch_all(&pool).await?;

    let mut ret: HashMap<Weekday, Vec<DayHour>> = hours.into_iter().fold(HashMap::new(), |mut map, v| {
        map.entry(Weekday::try_from(v.day as u8).unwrap_or(Weekday::Mon))
            .and_modify(|h| h.push(DayHour::new(v.from, v.to, v.discount)))
            .or_insert(vec![DayHour::new(v.from, v.to, v.discount)]);
        map
    });

    for d in 0..6 {
        if let None = ret.get(&Weekday::try_from(d as u8).unwrap()) {
            ret.insert(Weekday::try_from(d as u8).unwrap(), Vec::new());
        }
    }

    Ok(ret)
}

#[server]
pub async fn get_hours_for_day(day: Weekday) -> Result<Vec<DayHour>, ServerFnError> {
    Ok(hours_for_day(day).await?)
}

#[server]
pub async fn update_hours(hours: WeekHours) -> Result<ApiResponse<()>, ServerFnError> {
    use crate::perm_check;
    use crate::backend::get_pool;
    use crate::backend::data::DayHours;

    perm_check!(is_admin);

    let hr = DayHours::try_new(hours.hours())?;
    let pool = get_pool().await?;
    let day = hours.day();
    let mut tx = pool.begin().await?;

    sqlx::query("DELETE FROM opening_hour WHERE day = $1")
        .bind(day.num_days_from_monday() as i32)
        .execute(&mut *tx)
        .await?;

    for h in hr.hours() {
       sqlx::query(r#"INSERT INTO opening_hour(day, "from", "to", discount) VALUES($1, $2, $3, $4)"#)
           .bind(day.num_days_from_monday() as i32)
           .bind(h.from())
           .bind(h.to())
           .bind(h.discount())
           .execute(&mut *tx)
           .await?;
    }

    tx.commit().await?;

    Ok(ApiResponse::Data(()))
}

impl ForValidation for UpdateHours {
    fn entity(&self) -> &dyn Validate {
        &self.hours
    }
}