use gtk4 as gtk;

use async_channel::Sender;
use chrono::{Datelike, NaiveDate};
use gtk::glib;
use gtk::prelude::*;
use std::collections::HashMap;

use crate::{
    gui::update, gui::update::Msg, gui::App, model::category::Category, model::event,
    model::event::Event,
};

static DAYS: [&str; 7] = ["LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM"];
static MONTHES: [&str; 12] = [
    "Jan", "Fév", "Mar", "Avr", "Mai", "Juin", "Juil", "Aoû", "Sep", "Oct", "Nov", "Déc",
];

pub fn create(
    tx: Sender<Msg>,
    today: NaiveDate,
    start_date: NaiveDate,
    end_date: NaiveDate,
    events: &[Event],
    recurring_events: &[Event],
    categories: &[Category],
    default_color: &str,
) -> gtk::Grid {
    let grid = gtk::Grid::builder().build();

    for col in 0..7 {
        grid.attach(&day_title(col), col, 0, 1, 1);
    }

    let repetitions = event::repetitions_between(recurring_events, start_date, end_date);
    attach_days(
        tx.clone(),
        &grid,
        start_date,
        today,
        events,
        &repetitions,
        categories,
        default_color,
    );

    let event_controller_key = gtk::EventControllerKey::new();
    event_controller_key.connect_key_released(glib::clone!(
        #[strong]
        tx,
        move |_, _, keycode, _| {
            match keycode {
                111 => update::send(tx.clone(), Msg::SelectPreviousWeek), // UP
                116 => update::send(tx.clone(), Msg::SelectNextWeek),     // DOWN
                _ => (),
            }
        }
    ));
    grid.add_controller(event_controller_key);

    grid
}

fn attach_days(
    tx: Sender<Msg>,
    grid: &gtk::Grid,
    start_date: NaiveDate,
    today: NaiveDate,
    events: &[Event],
    repetitions: &HashMap<NaiveDate, Vec<Event>>,
    categories: &[Category],
    default_color: &str,
) {
    let mut d = start_date;
    for row in 1..5 {
        for col in 0..7 {
            grid.attach(
                &day_entry(
                    tx.clone(),
                    d,
                    today,
                    events,
                    repetitions,
                    categories,
                    default_color,
                ),
                col,
                row,
                1,
                1,
            );
            // TODO: error handling
            d = d.succ_opt().unwrap();
        }
    }
}

pub fn refresh_date(
    app: &App,
    date: NaiveDate,
    repetitions: &HashMap<NaiveDate, Vec<Event>>,
    categories: &[Category],
    default_color: &str,
) {
    let d = date.signed_duration_since(app.start_date).num_days();

    let col = (d % 7) as i32;
    let row = 1 + (d / 7) as i32;

    app.calendar.attach(
        &day_entry(
            app.tx.clone(),
            date,
            app.today,
            &app.events,
            repetitions,
            categories,
            default_color,
        ),
        col,
        row,
        1,
        1,
    )
}

fn day_title(col: i32) -> gtk::Box {
    let vbox = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .build();

    vbox.add_css_class("g-Calendar__DayTitle");

    let label = gtk::Label::builder().label(DAYS[col as usize]).build();

    vbox.append(&label);

    vbox
}

pub fn day_entry(
    tx: Sender<Msg>,
    date: NaiveDate,
    today: NaiveDate,
    events: &[Event],
    repetitions: &HashMap<NaiveDate, Vec<Event>>,
    categories: &[Category],
    default_color: &str,
) -> gtk::ScrolledWindow {
    let vbox = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .build();

    vbox.add_css_class("g-Calendar__Day");

    let gesture = gtk::GestureClick::new();
    gesture.connect_pressed(glib::clone!(
        #[strong]
        tx,
        move |_, n, _, _| {
            if n == 2 {
                update::send(tx.clone(), Msg::ShowAddForm { date });
            }
        }
    ));
    vbox.add_controller(gesture);

    if date == today {
        vbox.add_css_class("g-Calendar__Day--Today");
    }

    vbox.append(&day_label(today, date));

    let mut events = events
        .iter()
        .filter(|e| e.date == date)
        .collect::<Vec<&Event>>();
    let recurring_events = repetitions.get(&date).cloned().unwrap_or_default();
    events.extend(recurring_events.iter());
    events.sort_by_key(|e| e.start);

    if !events.is_empty() {
        vbox.append(&day_events(date, tx, events, categories, default_color));
    }

    gtk::ScrolledWindow::builder()
        .hscrollbar_policy(gtk::PolicyType::Never)
        .hexpand(true)
        .vexpand(true)
        .child(&vbox)
        .build()
}

fn day_label(today: NaiveDate, date: NaiveDate) -> gtk::Label {
    let label = gtk::Label::builder()
        .label(format!(
            "{} {}",
            date.day(),
            if date == today || date.day() == 1 {
                MONTHES[date.month0() as usize]
            } else {
                ""
            }
        ))
        .halign(gtk::Align::Start)
        .build();

    label.add_css_class("g-Calendar__DayNumber");
    if date.day() == 1 {
        label.add_css_class("g-Calendar__DayNumber--FirstOfMonth")
    }

    label
}

fn day_events(
    date: NaiveDate,
    tx: Sender<Msg>,
    events: Vec<&Event>,
    categories: &[Category],
    default_color: &str,
) -> gtk::Box {
    let vbox = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .build();

    for event in events {
        let hbox = gtk::Box::builder()
            .orientation(gtk::Orientation::Horizontal)
            .hexpand(true)
            .build();

        let background_color = categories
            .iter()
            .find(|c| event.category == Some(c.id))
            .map(|c| c.color.clone())
            .unwrap_or_else(|| default_color.to_string());

        let provider = gtk::CssProvider::new();
        provider.load_from_data(
            format!(
                "
          .event {{
            background-color: {};
            color: white;
            border-radius: 4px;
            padding: 4px;
            margin: 4px;
          }}

          .event:hover {{
            filter: brightness(120%);
          }}
        ",
                background_color
            )
            .as_str(),
        );
        hbox.style_context()
            .add_provider(&provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
        hbox.style_context().add_class("event");

        let gesture = gtk::GestureClick::new();
        gesture.connect_pressed(glib::clone!(
            #[strong]
            event,
            #[strong]
            tx,
            move |gesture, n, _, _| {
                gesture.set_state(gtk::EventSequenceState::Claimed);
                if n == 2 {
                    if event.repetition.is_some() {
                        update::send(
                            tx.clone(),
                            Msg::ShowRepetitionDialog {
                                date,
                                event_id: event.id,
                            },
                        );
                    } else {
                        update::send(tx.clone(), Msg::ShowUpdateForm { event_id: event.id });
                    }
                }
            }
        ));
        hbox.add_controller(gesture);

        let event_txt = &event.pprint();
        let label = gtk::Label::builder()
            .label(event_txt)
            .ellipsize(gtk::pango::EllipsizeMode::End)
            .tooltip_text(event_txt)
            .halign(gtk::Align::Start)
            .build();

        hbox.append(&label);
        vbox.append(&hbox);
    }

    vbox
}