mod repetition;
mod utils;

use gtk4 as gtk;

use anyhow::Result;
use chrono::{Duration, NaiveDate};
use gtk::glib;
use gtk::prelude::*;
use rusqlite::Connection;
use std::collections::HashSet;
use thiserror::Error;
use uuid::Uuid;

use crate::{
    db,
    gui::{update, update::Msg, App},
    model::{event, event::Event, repetition::Repetition},
};

pub async fn repetition_dialog(app: &App, date: NaiveDate, event: &Event) {
    let dialog = gtk::Dialog::builder()
        .transient_for(&*app.window)
        .modal(true)
        .title("Modifier")
        .css_classes(vec!["g-Dialog".to_string()])
        .build();

    let content_area = dialog.content_area();

    let lines = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .build();
    content_area.append(&lines);

    let button = gtk::Button::builder()
        .label("Cette occurence")
        .margin_bottom(10)
        .build();
    lines.append(&button);
    let tx = app.tx.clone();
    button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| {
        update::send(tx.clone(), Msg::ShowUpdateRepetitionForm { date, event_id: event.id });
        dialog.close()
    }));

    let button = gtk::Button::builder()
        .label("Toutes les occurences")
        .margin_bottom(10)
        .build();
    lines.append(&button);
    let tx = app.tx.clone();
    button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| {
        update::send(tx.clone(), Msg::ShowUpdateForm { event_id: event.id });
        dialog.close()
    }));

    let button = gtk::Button::builder()
        .label("À partir de cette occurence")
        .build();
    lines.append(&button);
    let tx = app.tx.clone();
    button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| {
        update::send(tx.clone(), Msg::ShowUpdateFromOccurence { date, event_id: event.id });
        dialog.close()
    }));

    dialog.run_future().await;
}

#[derive(Clone)]
pub enum Target {
    New { date: NaiveDate },
    Update { event: Event },
    UpdateRepetition { event: Event, date: NaiveDate },
    UpdateFromOccurence { event: Event, date: NaiveDate },
}

pub async fn show(app: &App, target: Target) {
    let event = match target {
        Target::New { .. } => None,
        Target::Update { ref event } => Some(event.clone()),
        Target::UpdateRepetition { ref event, .. } => Some(event.clone()),
        Target::UpdateFromOccurence { ref event, .. } => Some(event.clone()),
    };

    let title = if event.is_some() {
        "Modifier"
    } else {
        "Ajouter"
    };

    let dialog = gtk::Dialog::builder()
        .transient_for(&*app.window)
        .modal(true)
        .title(title)
        .css_classes(vec!["g-Dialog".to_string()])
        .build();

    let content_area = dialog.content_area();

    let lines = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .build();
    content_area.append(&lines);

    let columns = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(10)
        .build();
    lines.append(&columns);

    // First column

    let column1 = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .build();
    columns.append(&column1);

    let name = event
        .as_ref()
        .map(|e| utils::entry(&e.name))
        .unwrap_or_default();
    column1.append(&utils::label("Événement"));
    column1.append(&name);

    let date = match target {
        Target::New { date } => date,
        Target::Update { ref event } => event.date,
        Target::UpdateRepetition { date, .. } => date,
        Target::UpdateFromOccurence { date, .. } => date,
    };
    let date = utils::entry(&date.format(event::DATE_FORMAT).to_string());
    column1.append(&utils::label("Jour"));
    column1.append(&date);

    let start = event
        .as_ref()
        .map(|e| utils::time_entry(e.start))
        .unwrap_or_else(|| utils::entry(""));
    column1.append(&utils::label("Début"));
    column1.append(&start);

    let end = event
        .as_ref()
        .map(|e| utils::time_entry(e.end))
        .unwrap_or_else(|| utils::entry(""));
    column1.append(&utils::label("Fin"));
    column1.append(&end);

    // Second column

    let repetition = match target {
        Target::Update { ref event } => event.repetition.as_ref(),
        Target::UpdateFromOccurence { ref event, .. } => event.repetition.as_ref(),
        _ => None,
    };
    let repetition_model = repetition::view(repetition);
    columns.append(&repetition_model.view);

    // Buttons

    let button_title = match target {
        Target::New { .. } => "Créer",
        Target::Update { .. } => "Modifier",
        Target::UpdateRepetition { .. } => "Modifier l’occurence",
        Target::UpdateFromOccurence { .. } => "Modifier à partir de l’occurence",
    };

    let button = gtk::Button::builder()
        .label(button_title)
        .margin_bottom(10)
        .build();
    lines.append(&button);
    let conn = app.conn.clone();
    let tx = app.tx.clone();
    button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event => move |_| {
        let removed_occurences = match &target {
            Target::Update { event, .. } => {
                event.repetition.as_ref().map(|r| r.removed_occurences.clone()).unwrap_or_default()
            },
            _ => HashSet::new(),
        };
        match repetition::validate(&repetition_model, removed_occurences) {
            Ok(repetition) => {
                let id = match &target {
                    Target::Update {event} => event.id,
                    _ => Uuid::new_v4(),
                };
                match event::validate(id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition) {
                    Some(new) => {
                        match &target {
                            Target::New {..} => {
                                match db::insert(&conn, &new) {
                                    Ok(_) => {
                                        update::send(tx.clone(), Msg::AddEvent { new });
                                        dialog.close()
                                    },
                                    Err(err) => eprintln!("Error when inserting event: {}", err)
                                }
                            }
                            Target::Update {event} => {
                                match db::update(&conn, &new) {
                                    Ok(_) => {
                                        update::send(tx.clone(), Msg::UpdateEvent { old: event.clone(), new });
                                        dialog.close()
                                    },
                                    Err(err) => eprintln!("Error when updating event: {}", err)
                                }
                            }
                            Target::UpdateRepetition { event, date } => {
                                // TODO: improve intermediate error state
                                match delete_repetition_occurence(&conn, event, *date) {
                                    Ok(occurence) => {
                                        match db::insert(&conn, &new) {
                                            Ok(_) => {
                                                update::send(tx.clone(), Msg::UpdateEventOccurence {
                                                    event: event.clone(),
                                                    occurence,
                                                    date: *date,
                                                    new
                                                })
                                            }
                                            Err(err) => eprintln!("Error when updating repetition: {}", err)
                                        };
                                        dialog.close()
                                    },
                                    Err(err) => eprintln!("Error when updating repetition: {}", err)
                                }
                            }
                            Target::UpdateFromOccurence { date, event } => {
                                match update_repetition_until(&conn, *date - Duration::days(1), event) {
                                    Ok(updated) => {
                                        match db::insert(&conn, &new) {
                                            Ok(_) => {
                                                update::send(tx.clone(), Msg::UpdateRepeatedFrom {
                                                    old: event.clone(),
                                                    updated,
                                                    new
                                                });
                                                dialog.close()
                                            },
                                            Err(err) => eprintln!("Error when inserting event: {}", err)
                                        }
                                    },
                                    Err(err) => eprintln!("Error when updating event: {}", err)
                                }
                            }
                        }
                    }
                    None => eprintln!("Event is not valid.")
                }
            },
            Err(message) => eprintln!("{}", message)
        }
    }));

    if let Some(event) = event {
        let label = match target {
            Target::Update { .. } => "Supprimer",
            Target::UpdateFromOccurence { .. } => "Supprimer à partir de l’occurence",
            _ => "Supprimer l’occurence",
        };
        let button = gtk::Button::builder().label(label).build();
        lines.append(&button);
        let conn = app.conn.clone();
        let tx = app.tx.clone();
        button.connect_clicked(glib::clone!(@weak dialog => move |_| {
            match target {
                Target::UpdateRepetition { date, .. } => {
                    match delete_repetition_occurence(&conn, &event, date) {
                        Ok(occurence) => {
                            update::send(tx.clone(), Msg::DeleteOccurence { event: event.clone(), date, occurence });
                            dialog.close()
                        }
                        Err(err) => {
                            eprintln!("{:?}", err);
                        }
                    }
                }
                Target::UpdateFromOccurence { date, .. } => {
                    match update_repetition_until(&conn, date - Duration::days(1), &event) {
                        Ok(updated) => {
                            update::send(tx.clone(), Msg::UpdateEvent { old: event.clone(), new: updated });
                            dialog.close()
                        },
                        Err(err) => eprintln!("Error when updating event: {}", err)
                    }
                }
                _ => {
                    let operation = db::delete(&conn, &event.id);
                    if operation.is_ok() {
                        update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() });
                        dialog.close()
                    }
                }
            }
        }));
    }

    dialog.run_future().await;
}

#[derive(Error, Debug)]
enum DeleteError {
    #[error("Repetition not found")]
    RepetitionNotFound,
    #[error("Occurence not found")]
    OccurenceNotFound,
}

fn delete_repetition_occurence(
    conn: &Connection,
    event: &Event,
    occurence_date: NaiveDate,
) -> Result<usize> {
    if let Some(ref repetition) = event.repetition {
        if let Some(occurence) = repetition.occurence_index(event.date, occurence_date) {
            let mut event = event.clone();
            let mut repetition = repetition.clone();
            repetition.removed_occurences.insert(occurence);
            event.repetition = Some(repetition);
            db::update(conn, &event).map(|_| occurence)
        } else {
            Err(anyhow::Error::new(DeleteError::OccurenceNotFound))
        }
    } else {
        Err(anyhow::Error::new(DeleteError::RepetitionNotFound))
    }
}

fn update_repetition_until(conn: &Connection, date: NaiveDate, event: &Event) -> Result<Event> {
    let mut with_repetition_until = event.clone();
    with_repetition_until.repetition = event.repetition.as_ref().map(|r| Repetition {
        frequency: r.frequency.clone(),
        removed_occurences: r.removed_occurences.clone(),
        until: Some(date),
    });
    db::update(conn, &with_repetition_until)?;
    Ok(with_repetition_until)
}