use async_channel::{Receiver, Sender};
use chrono::{Duration, NaiveDate};
use std::collections::HashSet;
use std::iter::FromIterator;
use uuid::Uuid;

use crate::{
    db,
    gui::{calendar, form, utils, App},
    model::{event, event::Event},
};

pub fn send(tx: Sender<Msg>, msg: Msg) {
    utils::spawn(async move {
        let _ = tx.send(msg).await;
    })
}

pub enum Msg {
    ShowAddForm {
        date: NaiveDate,
    },
    ShowRepetitionDialog {
        event_id: Uuid,
        date: NaiveDate,
    },
    ShowUpdateForm {
        event_id: Uuid,
    },
    ShowUpdateRepetitionForm {
        event_id: Uuid,
        date: NaiveDate,
    },
    ShowUpdateFromOccurence {
        event_id: Uuid,
        date: NaiveDate,
    },
    AddEvent {
        new: Event,
    },
    UpdateEvent {
        old: Event,
        new: Event,
    },
    UpdateEventOccurence {
        event: Event,
        occurence: usize,
        date: NaiveDate,
        new: Event,
    },
    UpdateRepeatedFrom {
        old: Event,
        updated: Event,
        new: Event,
    },
    DeleteEvent {
        event: Event,
    },
    DeleteOccurence {
        event: Event,
        date: NaiveDate,
        occurence: usize,
    },
    SelectPreviousWeek,
    SelectNextWeek,
}

pub async fn event_handler(rx: Receiver<Msg>, mut app: App) {
    while let Ok(msg) = rx.recv().await {
        match msg {
            Msg::ShowAddForm { date } => form::show(&app, form::Target::New { date }).await,
            Msg::ShowRepetitionDialog { date, event_id } => {
                match app.recurring_events.iter().position(|e| e.id == event_id) {
                    Some(index) => {
                        form::repetition_dialog(&app, date, &app.recurring_events[index]).await
                    }
                    None => eprintln!("Event not found with id: {}", event_id),
                }
            }
            Msg::ShowUpdateForm { event_id } => {
                match app.recurring_events.iter().position(|e| e.id == event_id) {
                    Some(index) => {
                        form::show(
                            &app,
                            form::Target::Update {
                                event: app.recurring_events[index].clone(),
                            },
                        )
                        .await
                    }
                    None => match app.events.iter().position(|e| e.id == event_id) {
                        Some(index) => {
                            form::show(
                                &app,
                                form::Target::Update {
                                    event: app.events[index].clone(),
                                },
                            )
                            .await
                        }
                        None => eprintln!("Event not found with id: {}", event_id),
                    },
                }
            }
            Msg::ShowUpdateRepetitionForm { event_id, date } => {
                match app.recurring_events.iter().position(|e| e.id == event_id) {
                    Some(index) => {
                        form::show(
                            &app,
                            form::Target::UpdateRepetition {
                                date,
                                event: app.recurring_events[index].clone(),
                            },
                        )
                        .await
                    }
                    None => eprintln!("Event not found with id: {}", event_id),
                }
            }
            Msg::ShowUpdateFromOccurence { event_id, date } => {
                match app.recurring_events.iter().position(|e| e.id == event_id) {
                    Some(index) => {
                        let event = app.recurring_events[index].clone();
                        form::show(&app, form::Target::UpdateFromOccurence { date, event }).await
                    }
                    None => eprintln!("Event not found with id: {}", event_id),
                }
            }
            Msg::AddEvent { new } => {
                let refresh_dates = add(&mut app, &new);
                refresh(&app, &refresh_dates)
            }
            Msg::UpdateEvent { old, new } => {
                let mut refresh_dates = remove(&mut app, &old);
                refresh_dates.extend(add(&mut app, &new));
                refresh(&app, &refresh_dates);
            }
            Msg::UpdateEventOccurence {
                event,
                occurence,
                date,
                new,
            } => {
                remove_occurence(&mut app, &event, occurence);
                let mut refresh_dates = add(&mut app, &new);
                refresh_dates.insert(date);
                refresh(&app, &refresh_dates)
            }
            Msg::UpdateRepeatedFrom { old, updated, new } => {
                let mut refresh_dates = remove(&mut app, &old);
                refresh_dates.extend(add(&mut app, &updated));
                refresh_dates.extend(add(&mut app, &new));
                refresh(&app, &refresh_dates);
            }
            Msg::DeleteEvent { event } => {
                let refresh_dates = remove(&mut app, &event);
                refresh(&app, &refresh_dates)
            }
            Msg::DeleteOccurence {
                event,
                date,
                occurence,
            } => {
                remove_occurence(&mut app, &event, occurence);
                refresh(&app, &HashSet::from([date]))
            }
            Msg::SelectPreviousWeek => {
                app.start_date -= Duration::days(7);
                app.end_date -= Duration::days(7);

                match db::events::list_non_recurring_between(
                    &app.conn,
                    app.start_date,
                    app.end_date,
                ) {
                    Ok(events) => app.events = events,
                    Err(err) => eprintln!("{}", err),
                };
                refresh(&app, &HashSet::from_iter(visible_dates(&app)));
            }
            Msg::SelectNextWeek => {
                app.start_date += Duration::days(7);
                app.end_date += Duration::days(7);

                match db::events::list_non_recurring_between(
                    &app.conn,
                    app.start_date,
                    app.end_date,
                ) {
                    Ok(events) => app.events = events,
                    Err(err) => eprintln!("{}", err),
                };
                refresh(&app, &HashSet::from_iter(visible_dates(&app)));
            }
        }
    }
}

/// Remove event and return dates  that should be refreshed.
fn remove(app: &mut App, event: &Event) -> HashSet<NaiveDate> {
    if event.repetition.is_some() {
        match app.recurring_events.iter().position(|e| e.id == event.id) {
            Some(index) => {
                app.recurring_events.remove(index);
                repetition_dates(app, event)
            }
            None => {
                eprintln!("Event not found when trying to delete {:?}", event);
                HashSet::new()
            }
        }
    } else {
        match app.events.iter().position(|e| e.id == event.id) {
            Some(index) => {
                app.events.remove(index);
                HashSet::from([event.date])
            }
            None => {
                eprintln!("Event not found when trying to delete {:?}", event);
                HashSet::new()
            }
        }
    }
}

/// Remove event repetition
/// TODO: Completely remove the event if it’s the last remaining occurence
fn remove_occurence(app: &mut App, event: &Event, occurence: usize) {
    match app.recurring_events.iter().position(|e| e.id == event.id) {
        Some(index) => {
            let event = app.recurring_events.get_mut(index).unwrap();
            match event.repetition.clone() {
                Some(mut repetition) => {
                    repetition.removed_occurences.insert(occurence);
                    event.repetition = Some(repetition.clone())
                }
                None => eprintln!(
                    "Repetition not found when trying to delete repetition of {:?}",
                    event
                ),
            }
        }
        None => {
            eprintln!(
                "Event not found when trying to delete repetition of {:?}",
                event
            )
        }
    }
}

/// Add event and return dates that should be refreshed.
fn add(app: &mut App, event: &Event) -> HashSet<NaiveDate> {
    if event.repetition.is_some() {
        app.recurring_events.push(event.clone());
        let mut dates = repetition_dates(app, event);
        dates.insert(event.date);
        dates
    } else {
        app.events.push(event.clone());
        HashSet::from([event.date])
    }
}

/// Repetition dates of a repetead event.
fn repetition_dates(app: &App, event: &Event) -> HashSet<NaiveDate> {
    let event_reps = event::repetitions_between(&[event.clone()], app.start_date, app.end_date);
    HashSet::from_iter(event_reps.keys().copied())
}

/// Refresh app for the given dates.
fn refresh(app: &App, dates: &HashSet<NaiveDate>) {
    let repetitions =
        event::repetitions_between(&app.recurring_events, app.start_date, app.end_date);

    for date in dates {
        if date >= &app.start_date && date <= &app.end_date {
            calendar::refresh_date(
                app,
                *date,
                &repetitions,
                &app.categories,
                &app.default_color,
            )
        }
    }
}

/// List visible dates in calendar.
fn visible_dates(app: &App) -> Vec<NaiveDate> {
    let mut date = app.start_date;
    let mut res = vec![date];
    while date <= app.end_date {
        date += Duration::days(1);
        res.push(date);
    }
    res
}