use adw::prelude::*;
use chrono::{Datelike, NaiveDate};
use gtk::{gio, glib};
use outlay_core::db::Database;
use outlay_core::models::{Transaction, TransactionType};
use std::cell::{Cell, RefCell};
use std::rc::Rc;

use crate::icon_theme;
use crate::log_view::show_image_preview;

pub fn show_edit_dialog(
    parent: &impl IsA<gtk::Widget>,
    txn_id: i64,
    db: &Rc<Database>,
    toast_overlay: &adw::ToastOverlay,
    on_changed: impl Fn() + 'static,
) {
    let txn = match db.get_transaction(txn_id) {
        Ok(t) => t,
        Err(_) => return,
    };

    let dialog = adw::Dialog::builder()
        .title("Edit Transaction")
        .content_width(400)
        .content_height(500)
        .build();

    let toolbar = adw::ToolbarView::new();
    let header = adw::HeaderBar::new();
    toolbar.add_top_bar(&header);

    let content = gtk::Box::new(gtk::Orientation::Vertical, 16);
    content.set_margin_top(16);
    content.set_margin_bottom(16);
    content.set_margin_start(16);
    content.set_margin_end(16);

    // Type display (read-only)
    let type_label = match txn.transaction_type {
        TransactionType::Expense => "Expense",
        TransactionType::Income => "Income",
    };
    let type_row = adw::ActionRow::builder()
        .title("Type")
        .subtitle(type_label)
        .build();

    let amount_row = adw::EntryRow::builder()
        .title("Amount")
        .text(&format!("{:.2}", txn.amount))
        .build();
    amount_row.set_input_purpose(gtk::InputPurpose::Number);
    crate::numpad::attach_numpad(&amount_row);

    let cat_model = gtk::StringList::new(&[]);
    let mut cat_ids: Vec<i64> = Vec::new();
    let mut cat_selected: u32 = 0;
    if let Ok(cats) = db.list_categories(Some(txn.transaction_type)) {
        for (i, cat) in cats.iter().enumerate() {
            let icon_name = icon_theme::resolve_category_icon(&cat.icon, &cat.color);
            let entry = match &icon_name {
                Some(icon) => format!("{}\t{}", icon, cat.name),
                None => cat.name.clone(),
            };
            cat_model.append(&entry);
            if cat.id == txn.category_id {
                cat_selected = i as u32;
            }
            cat_ids.push(cat.id);
        }
    }
    let cat_row = adw::ComboRow::builder()
        .title("Category")
        .model(&cat_model)
        .selected(cat_selected)
        .build();
    cat_row.set_factory(Some(&make_category_factory()));
    cat_row.set_list_factory(Some(&make_category_factory()));
    let cat_ids = Rc::new(cat_ids);

    let date_fmt = db.get_date_format_string();
    let selected_date = Rc::new(Cell::new(txn.date));
    let date_label = gtk::Label::new(Some(&txn.date.format(&date_fmt).to_string()));
    date_label.set_halign(gtk::Align::End);
    date_label.set_hexpand(true);

    let calendar = gtk::Calendar::new();
    if let Ok(dt) = glib::DateTime::from_local(
        txn.date.year(),
        txn.date.month() as i32,
        txn.date.day() as i32,
        0, 0, 0.0,
    ) {
        calendar.set_year(dt.year());
        calendar.set_month(dt.month() - 1);
        calendar.set_day(dt.day_of_month());
    }

    let popover = gtk::Popover::new();
    popover.set_child(Some(&calendar));

    let date_menu_btn = gtk::MenuButton::new();
    date_menu_btn.set_popover(Some(&popover));
    let calendar_icon = gtk::Image::from_icon_name("outlay-calendar");
    calendar_icon.set_pixel_size(28);
    date_menu_btn.set_child(Some(&calendar_icon));
    date_menu_btn.add_css_class("flat");
    date_menu_btn.set_tooltip_text(Some("Pick date"));

    let date_box = gtk::Box::new(gtk::Orientation::Horizontal, 8);
    date_box.append(&date_label);
    date_box.append(&date_menu_btn);

    let date_row = adw::ActionRow::builder()
        .title("Date")
        .build();
    date_row.add_suffix(&date_box);

    let date_label_ref = date_label.clone();
    let popover_ref = popover.clone();
    let selected_date_ref = selected_date.clone();
    let date_fmt_clone = date_fmt.clone();
    calendar.connect_day_selected(move |cal| {
        let dt = cal.date();
        if let Some(d) = NaiveDate::from_ymd_opt(dt.year(), dt.month() as u32, dt.day_of_month() as u32) {
            selected_date_ref.set(d);
            date_label_ref.set_label(&d.format(&date_fmt_clone).to_string());
        }
        popover_ref.popdown();
    });

    let payee_row = adw::EntryRow::builder()
        .title("Payee (optional)")
        .text(txn.payee.as_deref().unwrap_or(""))
        .build();

    let note_row = adw::EntryRow::builder()
        .title("Note (optional)")
        .text(txn.note.as_deref().unwrap_or(""))
        .build();

    let existing_tags = db.get_transaction_tags(txn_id)
        .unwrap_or_default()
        .iter()
        .map(|t| t.name.clone())
        .collect::<Vec<_>>()
        .join(", ");
    let tags_row = adw::EntryRow::builder()
        .title("Tags (comma-separated)")
        .text(&existing_tags)
        .build();

    // Splits display
    let has_splits = db.has_splits(txn_id).unwrap_or(false);
    let existing_splits = if has_splits {
        db.get_splits(txn_id).unwrap_or_default()
    } else {
        Vec::new()
    };

    let splits_group = adw::PreferencesGroup::builder()
        .title("SPLITS")
        .build();
    splits_group.set_visible(has_splits);

    let split_list = gtk::ListBox::new();
    split_list.add_css_class("boxed-list");
    split_list.set_selection_mode(gtk::SelectionMode::None);

    // Track split entries for saving: (category_ids, DropDown, Entry, ListBoxRow)
    type EditSplitRow = (Vec<i64>, gtk::DropDown, gtk::Entry, gtk::ListBoxRow);
    let split_entries: Rc<RefCell<Vec<EditSplitRow>>> = Rc::new(RefCell::new(Vec::new()));

    let cats_for_splits = db.list_categories(Some(txn.transaction_type)).unwrap_or_default();
    let split_cat_ids: Vec<i64> = cats_for_splits.iter().map(|c| c.id).collect();
    let split_cat_names: Vec<String> = cats_for_splits.iter().map(|c| {
        let icon = crate::icon_theme::resolve_category_icon(&c.icon, &c.color);
        match icon {
            Some(i) => format!("{}\t{}", i, c.name),
            None => c.name.clone(),
        }
    }).collect();

    for split in &existing_splits {
        let label_refs: Vec<&str> = split_cat_names.iter().map(|s| s.as_str()).collect();
        let model = gtk::StringList::new(&label_refs);
        let dropdown = gtk::DropDown::new(Some(model), gtk::Expression::NONE);
        dropdown.set_factory(Some(&crate::category_combo::make_category_factory()));
        dropdown.set_list_factory(Some(&crate::category_combo::make_category_factory()));
        dropdown.set_hexpand(true);
        if let Some(pos) = split_cat_ids.iter().position(|&id| id == split.category_id) {
            dropdown.set_selected(pos as u32);
        }

        let amt_entry = gtk::Entry::new();
        amt_entry.set_text(&format!("{:.2}", split.amount));
        amt_entry.set_input_purpose(gtk::InputPurpose::Number);
        amt_entry.set_width_chars(8);

        let del_btn = gtk::Button::from_icon_name("outlay-delete");
        del_btn.add_css_class("flat");
        del_btn.add_css_class("circular");
        del_btn.set_valign(gtk::Align::Center);

        let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 8);
        hbox.set_margin_start(12);
        hbox.set_margin_end(8);
        hbox.set_margin_top(6);
        hbox.set_margin_bottom(6);
        hbox.append(&dropdown);
        hbox.append(&amt_entry);
        hbox.append(&del_btn);

        let row = gtk::ListBoxRow::new();
        row.set_child(Some(&hbox));
        row.set_activatable(false);
        split_list.append(&row);

        let row_clone = row.clone();
        let entries_ref = split_entries.clone();
        let list_ref = split_list.clone();
        del_btn.connect_clicked(move |_| {
            list_ref.remove(&row_clone);
            entries_ref.borrow_mut().retain(|(_, _, _, r)| r != &row_clone);
        });

        split_entries.borrow_mut().push((split_cat_ids.clone(), dropdown, amt_entry, row));
    }

    splits_group.add(&split_list);

    let form_group = adw::PreferencesGroup::new();
    form_group.add(&type_row);
    form_group.add(&amount_row);
    form_group.add(&cat_row);
    form_group.add(&payee_row);
    form_group.add(&date_row);
    form_group.add(&note_row);
    form_group.add(&tags_row);

    let btn_box = gtk::Box::new(gtk::Orientation::Horizontal, 12);
    btn_box.set_halign(gtk::Align::Center);
    btn_box.set_margin_top(8);

    let delete_btn = gtk::Button::with_label("Delete");
    delete_btn.add_css_class("destructive-action");
    delete_btn.add_css_class("pill");

    let save_btn = gtk::Button::with_label("Save");
    save_btn.add_css_class("suggested-action");
    save_btn.add_css_class("pill");

    btn_box.append(&delete_btn);
    btn_box.append(&save_btn);

    // -- Attachment UI --
    let attach_box = gtk::Box::new(gtk::Orientation::Vertical, 8);

    // Empty state: dashed drop-zone button
    let attach_placeholder = gtk::Button::new();
    attach_placeholder.add_css_class("flat");
    attach_placeholder.add_css_class("attach-drop-zone");
    {
        let ph = gtk::Box::new(gtk::Orientation::Vertical, 6);
        ph.set_margin_top(20);
        ph.set_margin_bottom(20);
        ph.set_halign(gtk::Align::Center);
        let icon = gtk::Image::from_icon_name("mail-attachment-symbolic");
        icon.set_pixel_size(24);
        icon.add_css_class("dim-label");
        let label = gtk::Label::new(Some("Attach receipt"));
        label.add_css_class("dim-label");
        label.add_css_class("caption");
        ph.append(&icon);
        ph.append(&label);
        attach_placeholder.set_child(Some(&ph));
    }

    // Thumbnails flow (hidden until populated)
    let attach_flow = gtk::FlowBox::new();
    attach_flow.set_selection_mode(gtk::SelectionMode::None);
    attach_flow.set_max_children_per_line(4);
    attach_flow.set_min_children_per_line(1);
    attach_flow.set_row_spacing(8);
    attach_flow.set_column_spacing(8);
    attach_flow.set_homogeneous(true);
    attach_flow.set_visible(false);

    // "Add another" button (visible when thumbnails showing)
    let attach_more_btn = gtk::Button::new();
    attach_more_btn.add_css_class("flat");
    attach_more_btn.set_halign(gtk::Align::Start);
    {
        let content_box = gtk::Box::new(gtk::Orientation::Horizontal, 6);
        let icon = gtk::Image::from_icon_name("list-add-symbolic");
        icon.set_pixel_size(16);
        let label = gtk::Label::new(Some("Add another"));
        label.add_css_class("caption");
        content_box.append(&icon);
        content_box.append(&label);
        attach_more_btn.set_child(Some(&content_box));
    }
    attach_more_btn.set_visible(false);

    attach_box.append(&attach_placeholder);
    attach_box.append(&attach_flow);
    attach_box.append(&attach_more_btn);

    // Load existing attachments from DB into the flow
    fn load_attachments(
        db: &Rc<Database>,
        txn_id: i64,
        flow: &gtk::FlowBox,
        placeholder: &gtk::Button,
        more_btn: &gtk::Button,
        toast: &adw::ToastOverlay,
    ) {
        while let Some(child) = flow.first_child() {
            flow.remove(&child);
        }
        let has_any = if let Ok(attachments) = db.list_attachments(txn_id) {
            for (att_id, filename, _mime, data) in &attachments {
                let thumb = gtk::Box::new(gtk::Orientation::Vertical, 0);
                thumb.set_overflow(gtk::Overflow::Hidden);
                thumb.add_css_class("attach-thumbnail");

                let overlay = gtk::Overlay::new();
                let bytes = glib::Bytes::from(data);
                let texture = gtk::gdk::Texture::from_bytes(&bytes).ok();
                let image = if let Some(tex) = &texture {
                    let pic = gtk::Picture::for_paintable(tex);
                    pic.set_content_fit(gtk::ContentFit::Cover);
                    pic.set_size_request(80, 80);
                    pic.upcast::<gtk::Widget>()
                } else {
                    let label = gtk::Label::new(Some(&filename));
                    label.set_size_request(80, 80);
                    label.upcast::<gtk::Widget>()
                };
                overlay.set_child(Some(&image));

                let del_btn = gtk::Button::from_icon_name("outlay-delete");
                del_btn.add_css_class("flat");
                del_btn.add_css_class("circular");
                del_btn.add_css_class("osd");
                del_btn.set_halign(gtk::Align::End);
                del_btn.set_valign(gtk::Align::Start);
                del_btn.set_tooltip_text(Some("Remove attachment"));
                overlay.add_overlay(&del_btn);

                // Click thumbnail to view full image
                if let Some(tex) = texture {
                    let click = gtk::GestureClick::new();
                    let fname = filename.clone();
                    let data_owned = data.clone();
                    click.connect_released(move |gesture, _, _, _| {
                        if let Some(widget) = gesture.widget() {
                            show_image_preview(&widget, &fname, &tex, &data_owned);
                        }
                    });
                    thumb.add_controller(click);
                }

                thumb.append(&overlay);

                let att_id = *att_id;
                let db_ref = db.clone();
                let flow_ref = flow.clone();
                let ph_ref = placeholder.clone();
                let mb_ref = more_btn.clone();
                let toast_ref = toast.clone();
                del_btn.connect_clicked(move |_| {
                    let _ = db_ref.delete_attachment(att_id);
                    load_attachments(&db_ref, txn_id, &flow_ref, &ph_ref, &mb_ref, &toast_ref);
                });

                flow.insert(&thumb, -1);
            }
            !attachments.is_empty()
        } else {
            false
        };

        flow.set_visible(has_any);
        placeholder.set_visible(!has_any);
        more_btn.set_visible(has_any);
    }
    load_attachments(db, txn_id, &attach_flow, &attach_placeholder, &attach_more_btn, toast_overlay);

    // Shared file picker for both buttons
    let open_picker: Rc<dyn Fn(&gtk::Button)> = {
        let db_ref = db.clone();
        let toast_ref = toast_overlay.clone();
        let flow_ref = attach_flow.clone();
        let ph_ref = attach_placeholder.clone();
        let mb_ref = attach_more_btn.clone();
        let dialog_widget = dialog.clone();
        Rc::new(move |btn: &gtk::Button| {
            let filter = gtk::FileFilter::new();
            filter.add_mime_type("image/png");
            filter.add_mime_type("image/jpeg");
            filter.add_mime_type("image/webp");
            filter.set_name(Some("Images"));

            let filters = gio::ListStore::new::<gtk::FileFilter>();
            filters.append(&filter);

            let file_dialog = gtk::FileDialog::builder()
                .title("Attach Receipt")
                .default_filter(&filter)
                .filters(&filters)
                .build();

            let db_att = db_ref.clone();
            let toast_att = toast_ref.clone();
            let flow_att = flow_ref.clone();
            let ph_att = ph_ref.clone();
            let mb_att = mb_ref.clone();
            let window = dialog_widget.root()
                .or_else(|| btn.root())
                .and_then(|r| r.downcast::<gtk::Window>().ok());
            file_dialog.open(window.as_ref(), gio::Cancellable::NONE, move |result: Result<gio::File, glib::Error>| {
                if let Ok(file) = result {
                    if let Some(path) = file.path() {
                        match std::fs::read(&path) {
                            Ok(data) if data.len() <= 5 * 1024 * 1024 => {
                                let filename = path.file_name()
                                    .and_then(|n| n.to_str())
                                    .unwrap_or("receipt")
                                    .to_string();
                                let mime = if filename.ends_with(".png") {
                                    "image/png"
                                } else if filename.ends_with(".webp") {
                                    "image/webp"
                                } else {
                                    "image/jpeg"
                                };
                                match db_att.insert_attachment(txn_id, &filename, mime, &data) {
                                    Ok(_) => {
                                        load_attachments(
                                            &db_att, txn_id, &flow_att,
                                            &ph_att, &mb_att, &toast_att,
                                        );
                                    }
                                    Err(e) => {
                                        let toast = adw::Toast::new(&format!("Error: {}", e));
                                        toast_att.add_toast(toast);
                                    }
                                }
                            }
                            Ok(_) => {
                                let toast = adw::Toast::new("File too large (max 5MB)");
                                toast_att.add_toast(toast);
                            }
                            Err(e) => {
                                let toast = adw::Toast::new(&format!("Read error: {}", e));
                                toast_att.add_toast(toast);
                            }
                        }
                    }
                }
            });
        })
    };

    {
        let picker = open_picker.clone();
        attach_placeholder.connect_clicked(move |btn| (picker)(btn));
    }
    {
        let picker = open_picker;
        attach_more_btn.connect_clicked(move |btn| (picker)(btn));
    }

    content.append(&form_group);
    content.append(&splits_group);
    content.append(&attach_box);
    content.append(&btn_box);

    let scroll = gtk::ScrolledWindow::builder()
        .hscrollbar_policy(gtk::PolicyType::Never)
        .child(&content)
        .build();

    toolbar.set_content(Some(&scroll));
    dialog.set_child(Some(&toolbar));

    let on_changed = Rc::new(on_changed);

    {
        let db_ref = db.clone();
        let dialog_ref = dialog.clone();
        let toast_ref = toast_overlay.clone();
        let amount_row_ref = amount_row.clone();
        let selected_date_ref = selected_date.clone();
        let note_row_ref = note_row.clone();
        let payee_row_ref = payee_row.clone();
        let tags_row_ref = tags_row.clone();
        let cat_row_ref = cat_row.clone();
        let cat_ids_ref = cat_ids.clone();
        let txn_clone = txn.clone();
        let on_changed = on_changed.clone();
        let split_entries_ref = split_entries.clone();
        save_btn.connect_clicked(move |_| {
            let amount_text = amount_row_ref.text();
            let amount: f64 = match outlay_core::expr::eval_expr(&amount_text) {
                Some(v) if v > 0.0 => v,
                _ => {
                    let toast = adw::Toast::new("Please enter a valid amount");
                    toast_ref.add_toast(toast);
                    return;
                }
            };

            let date = selected_date_ref.get();

            let note_text = note_row_ref.text();
            let note = if note_text.is_empty() {
                None
            } else {
                Some(note_text.to_string())
            };

            let cat_idx = cat_row_ref.selected() as usize;
            let category_id = cat_ids_ref.get(cat_idx).copied().unwrap_or(txn_clone.category_id);

            let payee_text = payee_row_ref.text();
            let payee = if payee_text.is_empty() {
                None
            } else {
                Some(payee_text.to_string())
            };

            let tags_text = tags_row_ref.text();
            let tag_names: Vec<String> = tags_text
                .split(',')
                .map(|s| s.trim().to_string())
                .filter(|s| !s.is_empty())
                .collect();

            let updated = Transaction {
                id: txn_clone.id,
                amount,
                transaction_type: txn_clone.transaction_type,
                category_id,
                currency: txn_clone.currency.clone(),
                exchange_rate: txn_clone.exchange_rate,
                note,
                date,
                created_at: txn_clone.created_at.clone(),
                recurring_id: txn_clone.recurring_id,
                payee,
            };

            match db_ref.update_transaction(&updated) {
                Ok(()) => {
                    let mut tag_ids = Vec::new();
                    for name in &tag_names {
                        if let Ok(tid) = db_ref.get_or_create_tag(name) {
                            tag_ids.push(tid);
                        }
                    }
                    let _ = db_ref.set_transaction_tags(txn_clone.id, &tag_ids);

                    let _ = db_ref.delete_splits(txn_clone.id);
                    let entries = split_entries_ref.borrow();
                    if !entries.is_empty() {
                        let splits: Vec<(i64, f64, Option<String>)> = entries.iter().filter_map(|(cat_ids, dropdown, amt_entry, _): &(Vec<i64>, gtk::DropDown, gtk::Entry, gtk::ListBoxRow)| {
                            let idx = dropdown.selected() as usize;
                            let cat_id = cat_ids.get(idx).copied()?;
                            let amt: f64 = outlay_core::expr::eval_expr(&amt_entry.text()).unwrap_or(0.0);
                            if amt > 0.0 { Some((cat_id, amt, None)) } else { None }
                        }).collect();
                        let _ = db_ref.insert_splits(txn_clone.id, &splits);
                    }

                    dialog_ref.close();
                    let toast = adw::Toast::new("Transaction updated");
                    toast_ref.add_toast(toast);
                    on_changed();
                }
                Err(e) => {
                    let toast = adw::Toast::new(&format!("Error: {}", e));
                    toast_ref.add_toast(toast);
                }
            }
        });
    }

    // Wire delete (5.1: undo-based deletion)
    {
        let db_ref = db.clone();
        let dialog_ref = dialog.clone();
        let toast_ref = toast_overlay.clone();
        let on_changed = on_changed.clone();
        let txn_clone = txn.clone();
        delete_btn.connect_clicked(move |_| {
            // Save attachments before deletion (CASCADE will remove them)
            let saved_attachments = db_ref.list_attachments(txn_id).unwrap_or_default();

            match db_ref.delete_transaction(txn_id) {
                Ok(()) => {
                    dialog_ref.close();
                    on_changed();

                    let toast = adw::Toast::new("Transaction deleted");
                    toast.set_button_label(Some("Undo"));
                    toast.set_timeout(5);

                    let db_undo = db_ref.clone();
                    let toast_undo = toast_ref.clone();
                    let txn_restore = txn_clone.clone();
                    let on_changed_undo = on_changed.clone();
                    toast.connect_button_clicked(move |_| {
                        use outlay_core::models::NewTransaction;
                        let new_txn = NewTransaction {
                            amount: txn_restore.amount,
                            transaction_type: txn_restore.transaction_type,
                            category_id: txn_restore.category_id,
                            currency: txn_restore.currency.clone(),
                            exchange_rate: txn_restore.exchange_rate,
                            note: txn_restore.note.clone(),
                            date: txn_restore.date,
                            recurring_id: txn_restore.recurring_id,
                            payee: txn_restore.payee.clone(),
                        };
                        match db_undo.insert_transaction(&new_txn) {
                            Ok(new_id) => {
                                // Restore attachments
                                for (_att_id, filename, mime, data) in &saved_attachments {
                                    let _ = db_undo.insert_attachment(new_id, filename, &mime, data);
                                }
                                let t = adw::Toast::new("Transaction restored");
                                toast_undo.add_toast(t);
                                on_changed_undo();
                            }
                            Err(e) => {
                                let t = adw::Toast::new(&format!("Restore failed: {}", e));
                                toast_undo.add_toast(t);
                            }
                        }
                    });

                    toast_ref.add_toast(toast);
                }
                Err(e) => {
                    let toast = adw::Toast::new(&format!("Error: {}", e));
                    toast_ref.add_toast(toast);
                }
            }
        });
    }

    dialog.present(Some(parent));
}

fn make_category_factory() -> gtk::SignalListItemFactory {
    let factory = gtk::SignalListItemFactory::new();
    factory.connect_setup(|_, item| {
        let item = item.downcast_ref::<gtk::ListItem>().unwrap();
        let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 8);
        let icon = gtk::Image::new();
        icon.set_pixel_size(20);
        let label = gtk::Label::new(None);
        hbox.append(&icon);
        hbox.append(&label);
        item.set_child(Some(&hbox));
    });
    factory.connect_bind(|_, item| {
        let item = item.downcast_ref::<gtk::ListItem>().unwrap();
        let string_obj = item.item().and_downcast::<gtk::StringObject>().unwrap();
        let text = string_obj.string();
        let hbox = item.child().and_downcast::<gtk::Box>().unwrap();
        let icon = hbox.first_child().and_downcast::<gtk::Image>().unwrap();
        let label = icon.next_sibling().and_downcast::<gtk::Label>().unwrap();
        if let Some((icon_name, name)) = text.split_once('\t') {
            icon.set_icon_name(Some(icon_name));
            icon.set_visible(true);
            label.set_label(name);
        } else {
            icon.set_visible(false);
            label.set_label(&text);
        }
    });
    factory
}
