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::{category::Category, 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);

    let dropdown_categories = get_dropdown_categories(&app.categories);
    let category_dropdown = gtk::DropDown::from_strings(
        &dropdown_categories
            .iter()
            .map(|s| s.as_str())
            .collect::<Vec<&str>>(),
    );
    category_dropdown.set_margin_bottom(10);
    let selected = get_selected_category(&event, &app.categories).unwrap_or_default();
    category_dropdown.set_selected(
        dropdown_categories
            .iter()
            .position(|d| d == &selected)
            .unwrap_or(0) as u32,
    );
    column1.append(&utils::label("Catégorie"));
    column1.append(&category_dropdown);

    // 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();
    let categories = app.categories.clone();
    button.connect_clicked(glib::clone!(
        #[weak]
        dialog,
        #[strong]
        target,
        #[strong]
        categories,
        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(),
                    };

                    // Find category id from selected id
                    let category = categories
                        .iter()
                        .find(|c| {
                            c.name == dropdown_categories[category_dropdown.selected() as usize]
                        })
                        .map(|c| c.id);

                    match event::validate(
                        id,
                        date.buffer().text().to_string(),
                        name.buffer().text().to_string(),
                        start.buffer().text().to_string(),
                        end.buffer().text().to_string(),
                        repetition,
                        category,
                    ) {
                        Some(new) => {
                            match &target {
                                Target::New { .. } => match db::events::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::events::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::events::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::events::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::events::delete(&conn, &event.id);
                        if operation.is_ok() {
                            update::send(
                                tx.clone(),
                                Msg::DeleteEvent {
                                    event: event.clone(),
                                },
                            );
                            dialog.close()
                        }
                    }
                }
            }
        ));
    }

    dialog.run_future().await;
}

fn get_dropdown_categories(categories: &[Category]) -> Vec<String> {
    let mut xs = categories
        .iter()
        .map(|c| c.name.clone())
        .collect::<Vec<String>>();
    xs.push("".to_string());
    xs.sort();
    xs
}

fn get_selected_category(event: &Option<Event>, categories: &[Category]) -> Option<String> {
    let id = event.as_ref()?.category?;
    let category = categories.iter().find(|c| c.id == id)?;
    Some(category.name.clone())
}

#[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::events::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::events::update(conn, &with_repetition_until)?;
    Ok(with_repetition_until)
}