use gtk::prelude::*;
use chrono::{Datelike, NaiveDate};
use outlay_core::db::Database;

/// Create a spending heatmap calendar for a given month.
pub fn calendar_heatmap(db: &Database, year: i32, month: u32) -> gtk::Box {
    let container = gtk::Box::new(gtk::Orientation::Vertical, 8);

    let raw_daily = db.get_daily_totals(year, month).unwrap_or_default();
    // Extract (day_number, expense_amount)
    let daily: Vec<(u32, f64)> = raw_daily.iter()
        .map(|(date, _income, expense)| (date.day(), *expense))
        .collect();
    let max_val = daily.iter().map(|(_, v)| *v).fold(0.0_f64, f64::max);

    // Day labels
    let header = gtk::Box::new(gtk::Orientation::Horizontal, 2);
    header.set_halign(gtk::Align::Center);
    for day_name in &["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] {
        let label = gtk::Label::new(Some(day_name));
        label.set_width_chars(5);
        label.add_css_class("caption");
        label.add_css_class("dim-label");
        header.append(&label);
    }
    container.append(&header);

    // Build daily amount map
    let mut amounts = std::collections::HashMap::new();
    for (day, amount) in &daily {
        amounts.insert(*day, *amount);
    }

    // Determine first day of month and number of days
    let first_day = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
    let days_in_month = if month == 12 {
        NaiveDate::from_ymd_opt(year + 1, 1, 1)
    } else {
        NaiveDate::from_ymd_opt(year, month + 1, 1)
    }.and_then(|d| d.pred_opt()).map(|d| d.day()).unwrap_or(30);

    // Monday = 0, Sunday = 6
    let first_weekday = first_day.weekday().num_days_from_monday();

    let grid = gtk::Grid::new();
    grid.set_row_spacing(2);
    grid.set_column_spacing(2);
    grid.set_halign(gtk::Align::Center);

    let mut day = 1_u32;
    let mut row = 0;
    let mut col = first_weekday as i32;

    while day <= days_in_month {
        let amount = amounts.get(&day).copied().unwrap_or(0.0);
        let cell = make_cell(day, amount, max_val);
        grid.attach(&cell, col, row, 1, 1);

        col += 1;
        if col > 6 {
            col = 0;
            row += 1;
        }
        day += 1;
    }

    container.append(&grid);

    let legend = gtk::Box::new(gtk::Orientation::Horizontal, 4);
    legend.set_halign(gtk::Align::Center);
    legend.set_margin_top(4);

    let less_label = gtk::Label::new(Some("Less"));
    less_label.add_css_class("caption");
    less_label.add_css_class("dim-label");
    legend.append(&less_label);

    for level in 0..5 {
        let cell = gtk::DrawingArea::new();
        cell.set_size_request(16, 16);
        let intensity = level as f64 / 4.0;
        cell.set_draw_func(move |_area, ctx, w, h| {
            let (r, g, b, a) = intensity_color(intensity);
            ctx.set_source_rgba(r, g, b, a);
            let radius = 3.0;
            let w = w as f64;
            let h = h as f64;
            ctx.new_sub_path();
            ctx.arc(w - radius, radius, radius, -std::f64::consts::FRAC_PI_2, 0.0);
            ctx.arc(w - radius, h - radius, radius, 0.0, std::f64::consts::FRAC_PI_2);
            ctx.arc(radius, h - radius, radius, std::f64::consts::FRAC_PI_2, std::f64::consts::PI);
            ctx.arc(radius, radius, radius, std::f64::consts::PI, 3.0 * std::f64::consts::FRAC_PI_2);
            ctx.close_path();
            let _ = ctx.fill();
        });
        legend.append(&cell);
    }

    let more_label = gtk::Label::new(Some("More"));
    more_label.add_css_class("caption");
    more_label.add_css_class("dim-label");
    legend.append(&more_label);

    container.append(&legend);
    container
}

fn make_cell(day: u32, amount: f64, max_val: f64) -> gtk::DrawingArea {
    let cell = gtk::DrawingArea::builder()
        .accessible_role(gtk::AccessibleRole::Img)
        .build();
    cell.set_size_request(44, 44);

    let intensity = if max_val > 0.0 { amount / max_val } else { 0.0 };
    let day_str = format!("{}", day);
    let has_spending = amount > 0.0;
    let tooltip = if has_spending {
        format!("Day {}: {:.2}", day, amount)
    } else {
        format!("Day {}: no spending", day)
    };
    cell.set_tooltip_text(Some(&tooltip));
    cell.update_property(&[gtk::accessible::Property::Label(&tooltip)]);

    cell.set_draw_func(move |_area, ctx, w, h| {
        let w = w as f64;
        let h = h as f64;

        // Background with rounded corners
        let radius = 4.0;
        let (r, g, b, a) = intensity_color(intensity);
        ctx.set_source_rgba(r, g, b, a);
        ctx.new_sub_path();
        ctx.arc(w - radius, radius, radius, -std::f64::consts::FRAC_PI_2, 0.0);
        ctx.arc(w - radius, h - radius, radius, 0.0, std::f64::consts::FRAC_PI_2);
        ctx.arc(radius, h - radius, radius, std::f64::consts::FRAC_PI_2, std::f64::consts::PI);
        ctx.arc(radius, radius, radius, std::f64::consts::PI, 3.0 * std::f64::consts::FRAC_PI_2);
        ctx.close_path();
        let _ = ctx.fill();

        // Day number
        ctx.set_source_rgba(1.0, 1.0, 1.0, if has_spending { 0.9 } else { 0.5 });
        ctx.select_font_face("sans-serif", gtk::cairo::FontSlant::Normal, gtk::cairo::FontWeight::Normal);
        ctx.set_font_size(12.0);
        let extents = ctx.text_extents(&day_str).unwrap();
        let x = (w - extents.width()) / 2.0 - extents.x_bearing();
        let y = (h - extents.height()) / 2.0 - extents.y_bearing();
        ctx.move_to(x, y);
        let _ = ctx.show_text(&day_str);
    });

    cell
}

fn intensity_color(intensity: f64) -> (f64, f64, f64, f64) {
    if intensity <= 0.0 {
        (0.5, 0.5, 0.5, 0.1)
    } else if intensity < 0.25 {
        (0.42, 0.75, 0.45, 0.4)
    } else if intensity < 0.5 {
        (0.85, 0.75, 0.35, 0.5)
    } else if intensity < 0.75 {
        (0.87, 0.55, 0.33, 0.6)
    } else {
        (0.87, 0.33, 0.36, 0.7)
    }
}
