use crate::shared::models::UserSession;
use crate::shared::state::AppState;
use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike};
use log::debug;
use rhai::Engine;
use std::sync::Arc;

fn parse_date(date_str: &str) -> Option<NaiveDate> {
    let trimmed = date_str.trim();
    NaiveDate::parse_from_str(trimmed, "%Y-%m-%d")
        .ok()
        .or_else(|| NaiveDate::parse_from_str(trimmed, "%d/%m/%Y").ok())
        .or_else(|| NaiveDate::parse_from_str(trimmed, "%m/%d/%Y").ok())
        .or_else(|| NaiveDate::parse_from_str(trimmed, "%Y/%m/%d").ok())
        .or_else(|| parse_datetime(trimmed).map(|dt| dt.date()))
}

fn parse_datetime(datetime_str: &str) -> Option<NaiveDateTime> {
    let trimmed = datetime_str.trim();
    NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%d %H:%M:%S")
        .ok()
        .or_else(|| NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%dT%H:%M:%S").ok())
        .or_else(|| NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%d %H:%M").ok())
        .or_else(|| parse_date(trimmed).and_then(|d| d.and_hms_opt(0, 0, 0)))
}

pub fn year_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("YEAR", |date_str: &str| -> i64 {
        parse_date(date_str)
            .map(|d| i64::from(d.year()))
            .unwrap_or(0)
    });
    engine.register_fn("year", |date_str: &str| -> i64 {
        parse_date(date_str)
            .map(|d| i64::from(d.year()))
            .unwrap_or(0)
    });

    debug!("Registered YEAR keyword");
}

pub fn month_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("MONTH", |date_str: &str| -> i64 {
        parse_date(date_str)
            .map(|d| i64::from(d.month()))
            .unwrap_or(0)
    });
    engine.register_fn("month", |date_str: &str| -> i64 {
        parse_date(date_str)
            .map(|d| i64::from(d.month()))
            .unwrap_or(0)
    });

    debug!("Registered MONTH keyword");
}

pub fn day_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("DAY", |date_str: &str| -> i64 {
        parse_date(date_str)
            .map(|d| i64::from(d.day()))
            .unwrap_or(0)
    });
    engine.register_fn("day", |date_str: &str| -> i64 {
        parse_date(date_str)
            .map(|d| i64::from(d.day()))
            .unwrap_or(0)
    });

    debug!("Registered DAY keyword");
}

pub fn hour_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("HOUR", |datetime_str: &str| -> i64 {
        parse_datetime(datetime_str)
            .map(|d| i64::from(d.hour()))
            .unwrap_or(0)
    });
    engine.register_fn("hour", |datetime_str: &str| -> i64 {
        parse_datetime(datetime_str)
            .map(|d| i64::from(d.hour()))
            .unwrap_or(0)
    });

    debug!("Registered HOUR keyword");
}

pub fn minute_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("MINUTE", |datetime_str: &str| -> i64 {
        parse_datetime(datetime_str)
            .map(|d| i64::from(d.minute()))
            .unwrap_or(0)
    });
    engine.register_fn("minute", |datetime_str: &str| -> i64 {
        parse_datetime(datetime_str)
            .map(|d| i64::from(d.minute()))
            .unwrap_or(0)
    });

    debug!("Registered MINUTE keyword");
}

pub fn second_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("SECOND", |datetime_str: &str| -> i64 {
        parse_datetime(datetime_str)
            .map(|d| i64::from(d.second()))
            .unwrap_or(0)
    });
    engine.register_fn("second", |datetime_str: &str| -> i64 {
        parse_datetime(datetime_str)
            .map(|d| i64::from(d.second()))
            .unwrap_or(0)
    });

    debug!("Registered SECOND keyword");
}

pub fn weekday_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("WEEKDAY", |date_str: &str| -> i64 {
        parse_date(date_str)
            .map(|d| i64::from(d.weekday().num_days_from_sunday()) + 1)
            .unwrap_or(0)
    });
    engine.register_fn("weekday", |date_str: &str| -> i64 {
        parse_date(date_str)
            .map(|d| i64::from(d.weekday().num_days_from_sunday()) + 1)
            .unwrap_or(0)
    });

    debug!("Registered WEEKDAY keyword");
}

pub fn format_date_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("FORMAT_DATE", |date_str: &str, format: &str| -> String {
        format_date_impl(date_str, format)
    });
    engine.register_fn("format_date", |date_str: &str, format: &str| -> String {
        format_date_impl(date_str, format)
    });

    debug!("Registered FORMAT_DATE keyword");
}

pub fn isdate_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
    engine.register_fn("ISDATE", |value: &str| -> bool {
        parse_date(value).is_some()
    });
    engine.register_fn("isdate", |value: &str| -> bool {
        parse_date(value).is_some()
    });
    engine.register_fn("IS_DATE", |value: &str| -> bool {
        parse_date(value).is_some()
    });

    debug!("Registered ISDATE keyword");
}

pub fn format_date_impl(date_str: &str, format: &str) -> String {
    if let Some(datetime) = parse_datetime(date_str) {
        let chrono_format = format
            .replace("YYYY", "%Y")
            .replace("yyyy", "%Y")
            .replace("YY", "%y")
            .replace("yy", "%y")
            .replace("MMMM", "%B")
            .replace("MMM", "%b")
            .replace("MM", "%m")
            .replace("DD", "%d")
            .replace("dd", "%d")
            .replace("HH", "%H")
            .replace("hh", "%I")
            .replace("mm", "%M")
            .replace("ss", "%S")
            .replace("AM/PM", "%p")
            .replace("am/pm", "%P");

        datetime.format(&chrono_format).to_string()
    } else {
        date_str.to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::{Datelike, Timelike};

    #[test]
    fn test_parse_date() {
        let date = parse_date("2025-01-22");
        assert!(date.is_some());
        let d = date.unwrap();
        assert_eq!(d.year(), 2025);
        assert_eq!(d.month(), 1);
        assert_eq!(d.day(), 22);
    }

    #[test]
    fn test_parse_datetime() {
        let dt = parse_datetime("2025-01-22 14:30:45");
        assert!(dt.is_some());
        let d = dt.unwrap();
        assert_eq!(d.hour(), 14);
        assert_eq!(d.minute(), 30);
        assert_eq!(d.second(), 45);
    }

    #[test]
    fn test_invalid_date() {
        let date = parse_date("invalid");
        assert!(date.is_none());
    }
}
