use crate::parser::Event;
use chrono::NaiveDate;

pub fn filter_events(events: &[Event], from: NaiveDate, to: NaiveDate) -> Vec<&Event> {
    let mut filtered: Vec<&Event> = events
        .iter()
        .filter(|e| {
            let d = e.start_date();
            d >= from && d <= to
        })
        .collect();
    filtered.sort_by_key(|e| {
        use crate::parser::EventTime;
        use chrono::Timelike;
        let date = e.start_date();
        let mins = match &e.start {
            EventTime::AllDay(_) => i32::MIN,
            EventTime::DateTime(dt) => {
                let local = dt.with_timezone(&chrono::Local);
                local.hour() as i32 * 60 + local.minute() as i32
            }
        };
        (date, mins)
    });
    filtered
}

pub fn format_events(events: &[&Event]) -> String {
    use crate::parser::EventTime;
    use chrono::Datelike;
    use colored::Colorize;

    let weekday_abbr = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
    let mut output = String::new();
    let mut current_date: Option<NaiveDate> = None;

    for event in events {
        let date = event.start_date();
        if current_date != Some(date) {
            if !output.is_empty() {
                output.push('\n');
            }
            let wd = date.weekday().num_days_from_monday() as usize;
            output.push_str(&format!(
                "{} ({})\n",
                date.format("%Y-%m-%d"),
                weekday_abbr[wd]
            ));
            current_date = Some(date);
        }
        let time_str = match &event.start {
            EventTime::AllDay(_) => "  All day".to_string(),
            EventTime::DateTime(dt) => {
                use chrono::Timelike;
                let local_dt = dt.with_timezone(&chrono::Local);
                format!("  {:02}:{:02}", local_dt.hour(), local_dt.minute())
            }
        };
        let cal_label = format!("[{}]", event.calendar_name)
            .color(event.color.as_str())
            .to_string();
        output.push_str(&format!("{}   {}  {}\n", time_str, cal_label, event.summary));
    }
    output
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parser::{Event, EventTime};
    use chrono::NaiveDate;

    fn allday(y: i32, m: u32, d: u32, summary: &str, cal: &str) -> Event {
        Event {
            summary: summary.to_string(),
            start: EventTime::AllDay(NaiveDate::from_ymd_opt(y, m, d).unwrap()),
            calendar_name: cal.to_string(),
            color: "".to_string(),
            rrule: None,
            uid: None,
        }
    }

    fn datetime_jst(
        y: i32,
        m: u32,
        d: u32,
        h: u32,
        min: u32,
        summary: &str,
        cal: &str,
    ) -> Event {
        use chrono::TimeZone;
        let tz: chrono_tz::Tz = "Asia/Tokyo".parse().unwrap();
        let dt = tz
            .with_ymd_and_hms(y, m, d, h, min, 0)
            .single()
            .unwrap();
        Event {
            summary: summary.to_string(),
            start: EventTime::DateTime(dt),
            calendar_name: cal.to_string(),
            color: "".to_string(),
            rrule: None,
            uid: None,
        }
    }

    #[test]
    fn test_filter_events_within_range() {
        let events = vec![
            allday(2026, 2, 22, "past", "test"),
            allday(2026, 2, 23, "today", "test"),
            allday(2026, 2, 25, "in-range", "test"),
            allday(2026, 2, 28, "out-of-range", "test"),
        ];
        let from = NaiveDate::from_ymd_opt(2026, 2, 23).unwrap();
        let to = NaiveDate::from_ymd_opt(2026, 2, 27).unwrap();
        let result = filter_events(&events, from, to);
        assert_eq!(result.len(), 2);
        assert_eq!(result[0].summary, "today");
        assert_eq!(result[1].summary, "in-range");
    }

    #[test]
    fn test_filter_events_sorted_by_time_within_day() {
        let events = vec![
            datetime_jst(2026, 2, 23, 15, 0, "afternoon", "test"),
            datetime_jst(2026, 2, 23, 9, 0, "morning", "test"),
            datetime_jst(2026, 2, 23, 12, 0, "noon", "test"),
        ];
        let from = NaiveDate::from_ymd_opt(2026, 2, 23).unwrap();
        let to = NaiveDate::from_ymd_opt(2026, 2, 23).unwrap();
        let result = filter_events(&events, from, to);
        assert_eq!(result[0].summary, "morning");
        assert_eq!(result[1].summary, "noon");
        assert_eq!(result[2].summary, "afternoon");
    }

    #[test]
    fn test_filter_events_sorted_by_date() {
        let events = vec![
            allday(2026, 2, 25, "later", "test"),
            allday(2026, 2, 23, "earlier", "test"),
            allday(2026, 2, 24, "middle", "test"),
        ];
        let from = NaiveDate::from_ymd_opt(2026, 2, 23).unwrap();
        let to = NaiveDate::from_ymd_opt(2026, 2, 25).unwrap();
        let result = filter_events(&events, from, to);
        assert_eq!(result[0].summary, "earlier");
        assert_eq!(result[1].summary, "middle");
        assert_eq!(result[2].summary, "later");
    }

    #[test]
    fn test_filter_events_empty() {
        let events: Vec<Event> = vec![];
        let from = NaiveDate::from_ymd_opt(2026, 2, 23).unwrap();
        let to = NaiveDate::from_ymd_opt(2026, 2, 27).unwrap();
        let result = filter_events(&events, from, to);
        assert!(result.is_empty());
    }

    #[test]
    fn test_format_events_allday() {
        let events = vec![allday(2026, 2, 23, "Holiday", "school")];
        let refs: Vec<&Event> = events.iter().collect();
        let output = format_events(&refs);
        assert!(output.contains("2026-02-23"));
        assert!(output.contains("Mon"));
        assert!(output.contains("All day"));
        assert!(output.contains("school"));
        assert!(output.contains("Holiday"));
    }

    #[test]
    fn test_format_events_datetime() {
        let events = vec![datetime_jst(2026, 2, 23, 9, 0, "Weekly MTG", "work")];
        let refs: Vec<&Event> = events.iter().collect();
        let output = format_events(&refs);
        assert!(output.contains("09:00"));
        assert!(output.contains("work"));
        assert!(output.contains("Weekly MTG"));
    }

    #[test]
    fn test_format_events_groups_by_date() {
        let events = vec![
            allday(2026, 2, 23, "Event A", "A"),
            datetime_jst(2026, 2, 23, 15, 0, "Event B", "B"),
            allday(2026, 2, 24, "Event C", "C"),
        ];
        let refs: Vec<&Event> = events.iter().collect();
        let output = format_events(&refs);
        assert_eq!(output.matches("2026-02-23").count(), 1);
        assert_eq!(output.matches("2026-02-24").count(), 1);
    }

    #[test]
    fn test_format_events_calendar_color() {
        colored::control::set_override(true);
        let mut event = allday(2026, 2, 23, "meeting", "work");
        event.color = "blue".to_string();
        let refs: Vec<&Event> = vec![&event];
        let output = format_events(&refs);
        // ANSI escape code should be present when color is set
        assert!(output.contains("\x1b["));
        assert!(output.contains("work"));
        colored::control::set_override(false);
    }
}
