//! Golden cross-SDK fixture tests.
//!
//! Each test case builds a widget and asserts its JSON serialization equals
//! the corresponding fixture file under ../fixtures/widgets/.
//! Each event test parses the canonical incoming payload and asserts the
//! parser returns the documented typed event.

use std::fs;
use std::path::PathBuf;

use glimpse_sdk::{
    ActionItem, Align, Badge, BoxNode, Button, ButtonVariant, CallbackEvent, Card, Checkbox, Column,
    ContentFit, Copyable, EmptyState, Expander, Grid, GridChild, Hero, Icon, Image, Item, Label,
    LevelBar, LevelBarMode, LinkButton, ListBox, MenuButton, Meter, Overlay, PagerItem, PagerStrip,
    Picture, Progress, PropertyList, Row, Scroll, Section, Select, SelectOption, Separator, Slider,
    Spinner, StatusDot, Switch, ToggleButton, TreeExpander, TreeNode, Variant, parse_callback_event,
    tree,
};
use serde_json::Value;

fn fixtures_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("..")
        .join("fixtures")
}

fn load(rel: &str) -> Value {
    let path = fixtures_root().join(rel);
    let text = fs::read_to_string(&path)
        .unwrap_or_else(|e| panic!("read fixture {}: {e}", path.display()));
    serde_json::from_str(&text).unwrap_or_else(|e| panic!("parse fixture {}: {e}", path.display()))
}

#[track_caller]
fn assert_widget(name: &str, node: TreeNode) {
    let expected = load(&format!("widgets/{name}.json"));
    let got = serde_json::to_value(&node).expect("serialize");
    assert_eq!(got, expected, "fixture mismatch for widgets/{name}.json");
}

#[test]
fn widget_label_basic() {
    assert_widget("label-basic", Label::new("Hello").into());
}

#[test]
fn widget_label_modifiers() {
    let mut label = Label::new("Hello");
    label.wrap = true;
    label.xalign = Some(0.5);
    label.selectable = true;
    assert_widget("label-modifiers", label.into());
}

#[test]
fn widget_button_basic() {
    assert_widget("button-basic", Button::new("go").label("Go").into());
}

#[test]
fn widget_button_with_icon() {
    let mut b = Button::new("go").label("Go");
    b.icon = Some("go-symbolic".into());
    assert_widget("button-with-icon", b.into());
}

#[test]
fn widget_button_icon_only() {
    let mut b = Button::new("go");
    b.icon = Some("go-symbolic".into());
    assert_widget("button-icon-only", b.into());
}

#[test]
fn widget_button_primary() {
    assert_widget(
        "button-primary",
        Button::new("go")
            .label("Go")
            .variant(ButtonVariant::Primary)
            .into(),
    );
}

#[test]
fn widget_button_disabled() {
    assert_widget(
        "button-disabled",
        Button::new("go").label("Go").enabled(false).into(),
    );
}

#[test]
fn widget_link_button() {
    assert_widget("link-button", LinkButton::new("https://example.com").into());
}

#[test]
fn widget_link_button_label() {
    assert_widget(
        "link-button-label",
        LinkButton::new("https://example.com/docs")
            .label("Docs")
            .into(),
    );
}

#[test]
fn widget_expander() {
    assert_widget(
        "expander",
        Expander::new("Details").child(Label::new("More")).into(),
    );
}

#[test]
fn widget_expander_expanded() {
    assert_widget(
        "expander-expanded",
        Expander::new("Details")
            .expanded(true)
            .child(Label::new("More"))
            .into(),
    );
}

#[test]
fn widget_overlay() {
    assert_widget(
        "overlay",
        Overlay::new(Label::new("Base"))
            .overlay(Badge::new("Top"))
            .into(),
    );
}

#[test]
fn widget_list_box() {
    assert_widget(
        "list-box",
        ListBox::new(tree![Label::new("First"), Badge::new("Second")]).into(),
    );
}

#[test]
fn widget_level_bar() {
    assert_widget(
        "level-bar",
        LevelBar::new(0.7)
            .min(0.0)
            .max(1.0)
            .mode(LevelBarMode::Continuous)
            .into(),
    );
}

#[test]
fn widget_tree_expander() {
    assert_widget(
        "tree-expander",
        TreeExpander::new(Label::new("Nested"))
            .hide_expander(true)
            .indent_for_depth(true)
            .indent_for_icon(true)
            .into(),
    );
}

#[test]
fn widget_menu_button() {
    assert_widget(
        "menu-button",
        MenuButton::new(Label::new("Menu content"))
            .label("More")
            .icon("open-menu-symbolic")
            .into(),
    );
}

#[test]
fn widget_switch_on() {
    let mut s = Switch::new("vpn");
    s.label = Some("VPN".into());
    s.active = true;
    assert_widget("switch-on", s.into());
}

#[test]
fn widget_switch_off() {
    assert_widget("switch-off", Switch::new("vpn").into());
}

#[test]
fn widget_toggle_button_on() {
    let mut toggle = ToggleButton::new("wifi");
    toggle.label = Some("Wi-Fi".into());
    toggle.active = true;
    assert_widget("toggle-button-on", toggle.into());
}

#[test]
fn widget_toggle_button_off() {
    assert_widget("toggle-button-off", ToggleButton::new("wifi").into());
}

#[test]
fn widget_checkbox_on() {
    let mut c = Checkbox::new("autostart");
    c.label = Some("Run at login".into());
    c.active = true;
    assert_widget("checkbox-on", c.into());
}

#[test]
fn widget_slider() {
    let mut s = Slider::new("brightness");
    s.min = 0.0;
    s.max = 1.0;
    s.step = 0.05;
    s.value = 0.6;
    assert_widget("slider", s.into());
}

#[test]
fn widget_select() {
    let mut d = Select::new(
        "env",
        vec![
            SelectOption::new("prod", "Production"),
            SelectOption::new("stage", "Staging"),
        ],
    );
    d.selected = Some(0);
    assert_widget("select", d.into());
}

#[test]
fn widget_select_empty() {
    assert_widget("select-empty", Select::new("env", vec![]).into());
}

#[test]
fn widget_badge() {
    assert_widget("badge", Badge::new("42%").into());
}

#[test]
fn widget_badge_success_variant() {
    let mut b = Badge::new("OK");
    b.common.variant = Some(Variant::Success);
    assert_widget("badge-success-variant", b.into());
}

#[test]
fn widget_hero_basic() {
    assert_widget("hero-basic", Hero::new("Counter", "Value: 0").into());
}

#[test]
fn widget_hero_with_icon() {
    assert_widget(
        "hero-with-icon",
        Hero::new("VPN", "Connected")
            .icon(Icon::name("network-vpn-symbolic"))
            .into(),
    );
}

#[test]
fn widget_progress() {
    assert_widget("progress", Progress::new(0.7).max(1.0).into());
}

#[test]
fn widget_progress_with_text() {
    assert_widget(
        "progress-with-text",
        Progress::new(0.7)
            .max(1.0)
            .show_text(true)
            .text("70%")
            .into(),
    );
}

#[test]
fn widget_spinner_default() {
    assert_widget("spinner-default", Spinner::new().into());
}

#[test]
fn widget_spinner_stopped() {
    assert_widget("spinner-stopped", Spinner::new().spinning(false).into());
}

#[test]
fn widget_status_dot() {
    assert_widget("status-dot", StatusDot::new().into());
}

#[test]
fn widget_status_dot_warning() {
    let mut dot = StatusDot::new();
    dot.common.variant = Some(Variant::Warning);
    assert_widget("status-dot-warning", dot.into());
}

#[test]
fn widget_pager_item_number_active() {
    assert_widget(
        "pager-item-number-active",
        PagerItem::number("1").id("workspace-1").active(true).into(),
    );
}

#[test]
fn widget_pager_strip() {
    assert_widget(
        "pager-strip",
        PagerStrip::new(vec![
            PagerItem::number("1").id("workspace-1").active(true),
            PagerItem::number("2").id("workspace-2").occupied(true),
            PagerItem::dots().id("workspace-3").urgent(true),
        ])
        .into(),
    );
}

#[test]
fn widget_image_by_name() {
    assert_widget(
        "image-by-name",
        Image::new(Icon::name("user-info-symbolic")).into(),
    );
}

#[test]
fn widget_image_by_path() {
    let mut img = Image::new(Icon::path("/home/me/avatar.png"));
    img.pixel_size = Some(64);
    assert_widget("image-by-path", img.into());
}

#[test]
fn widget_picture() {
    assert_widget("picture", Picture::new("/home/me/photo.png").into());
}

#[test]
fn widget_picture_content_fit() {
    assert_widget(
        "picture-content-fit",
        Picture::new("/home/me/photo.png")
            .content_fit(ContentFit::Cover)
            .into(),
    );
}

#[test]
fn widget_separator() {
    assert_widget("separator", Separator::new().into());
}

#[test]
fn widget_box_vertical() {
    assert_widget("box-vertical", BoxNode::vertical(vec![]).spacing(8).into());
}

#[test]
fn widget_box_horizontal() {
    assert_widget(
        "box-horizontal",
        BoxNode::horizontal(vec![]).spacing(4).into(),
    );
}

#[test]
fn widget_row() {
    assert_widget("row", Row::new(vec![]).spacing(8).into());
}

#[test]
fn widget_column() {
    assert_widget("column", Column::new(vec![]).spacing(8).into());
}

#[test]
fn widget_grid() {
    let mut grid = Grid::new(vec![GridChild::new(0, 0, Label::new("A").into()), {
        let mut c = GridChild::new(0, 1, Label::new("B").into());
        c.width = 2;
        c
    }]);
    grid.row_spacing = 4;
    grid.column_spacing = 4;
    assert_widget("grid", grid.into());
}

#[test]
fn widget_scroll() {
    assert_widget(
        "scroll",
        Scroll::new(Label::new("scrollable").into()).into(),
    );
}

#[test]
fn widget_card() {
    assert_widget("card", Card::new(vec![Label::new("in card").into()]).into());
}

#[test]
fn widget_card_empty() {
    assert_widget("card-empty", Card::new(vec![]).into());
}

#[test]
fn widget_section_basic() {
    assert_widget(
        "section-basic",
        Section::new("System", vec![Label::new("uptime").into()]).into(),
    );
}

#[test]
fn widget_section_empty_children() {
    assert_widget(
        "section-empty-children",
        Section::new("Empty", vec![]).into(),
    );
}

#[test]
fn widget_property_list() {
    assert_widget(
        "property-list",
        PropertyList::new([("IPv4", "10.0.0.42"), ("SSID", "home-5G")]).into(),
    );
}

#[test]
fn widget_property_list_title() {
    assert_widget(
        "property-list-title",
        PropertyList::new([("IPv4", "10.0.0.42"), ("SSID", "home-5G")])
            .title("Network")
            .into(),
    );
}

#[test]
fn widget_property_list_empty() {
    assert_widget("property-list-empty", PropertyList::default().into());
}

#[test]
fn widget_item() {
    assert_widget("item", Item::new("Wi-Fi").into());
}

#[test]
fn widget_item_with_right() {
    assert_widget(
        "item-with-right",
        Item::new("Wi-Fi")
            .icon("network-wireless-symbolic")
            .sublabel("Connected")
            .right(Badge::new("home-5G"))
            .into(),
    );
}

#[test]
fn widget_action_item() {
    assert_widget("action-item", ActionItem::new("wifi", "Wi-Fi").into());
}

#[test]
fn widget_action_item_with_right() {
    assert_widget(
        "action-item-with-right",
        ActionItem::new("wifi", "Wi-Fi")
            .icon("network-wireless-symbolic")
            .sublabel("Connected")
            .right(Badge::new("home-5G"))
            .into(),
    );
}

#[test]
fn widget_empty_state() {
    assert_widget("empty-state", EmptyState::new("Nothing here").into());
}

#[test]
fn widget_empty_state_with_subtitle() {
    assert_widget(
        "empty-state-with-subtitle",
        EmptyState::new("Nothing here")
            .subtitle("Plug in a device.")
            .into(),
    );
}

#[test]
fn widget_meter() {
    assert_widget("meter", Meter::new("Memory", 0.51, 1.0).into());
}

#[test]
fn widget_meter_interactive() {
    let mut m = Meter::new("Volume", 0.42, 1.0);
    m.icon = Some(Icon::name("audio-volume-medium-symbolic"));
    m.text = Some("42%".into());
    m.interactive = true;
    assert_widget("meter-interactive", m.into());
}

#[test]
fn widget_copyable() {
    assert_widget("copyable", Copyable::new("IPv4", "10.0.0.42").into());
}

#[test]
fn widget_common_props_all() {
    let mut label = Label::new("marked");
    label.common.id = Some("marked".into());
    label.common.visible = Some(false);
    label.common.hexpand = Some(true);
    label.common.vexpand = Some(true);
    label.common.halign = Some(Align::Center);
    label.common.valign = Some(Align::End);
    label.common.tooltip = Some("details".into());
    label.common.variant = Some(Variant::Warning);
    assert_widget("common-props-all", label.into());
}

#[test]
fn widget_tree_hero_column_section() {
    use glimpse_sdk::tree;
    let column = Column::new(tree![
        Hero::new("Counter", "Value: 0"),
        Section::new(
            "Controls",
            tree![
                Label::new("Current"),
                Button::new("increment").label("Increment"),
            ],
        ),
    ])
    .spacing(8);
    assert_widget("tree-hero-column-section", column.into());
}

#[test]
fn widget_tree_card_with_grid() {
    let grid = {
        let mut g = Grid::new(vec![
            GridChild::new(0, 0, Label::new("K").into()),
            GridChild::new(0, 1, Badge::new("V").into()),
        ]);
        g.row_spacing = 4;
        g.column_spacing = 8;
        g
    };
    let card = Card::new(vec![grid.into()]);
    assert_widget("tree-card-with-grid", card.into());
}

// ---------- events ----------

fn load_event(name: &str) -> (Value, Value) {
    let raw = load(&format!("events/{name}.json"));
    (raw["incoming"].clone(), raw["parsed"].clone())
}

#[track_caller]
fn assert_event(name: &str, check: impl FnOnce(CallbackEvent, &Value)) {
    let (incoming, parsed) = load_event(name);
    let event = parse_callback_event(incoming).expect("parse");
    check(event, &parsed);
}

#[test]
fn event_click_left() {
    assert_event("click-left", |e, parsed| {
        let CallbackEvent::Click(c) = e else {
            panic!("expected click event");
        };
        assert_eq!(c.id, parsed["id"].as_str().unwrap());
        assert_eq!(
            c.button.as_deref().unwrap(),
            parsed["button"].as_str().unwrap()
        );
    });
}

#[test]
fn event_click_no_button() {
    assert_event("click-no-button", |e, parsed| {
        let CallbackEvent::Click(c) = e else {
            panic!("expected click event");
        };
        assert_eq!(c.id, parsed["id"].as_str().unwrap());
        assert!(c.button.is_none());
    });
}

#[test]
fn event_scroll() {
    assert_event("scroll-down", |e, parsed| {
        let CallbackEvent::Scroll(s) = e else {
            panic!("expected scroll");
        };
        assert_eq!(s.id, parsed["id"].as_str().unwrap());
        assert_eq!(s.delta_y, Some(parsed["delta_y"].as_f64().unwrap()));
    });
}

#[test]
fn event_input() {
    assert_event("input", |e, parsed| {
        let CallbackEvent::Input(i) = e else {
            panic!("expected input");
        };
        assert_eq!(i.id, parsed["id"].as_str().unwrap());
        assert_eq!(i.text, parsed["text"].as_str().unwrap());
    });
}

#[test]
fn event_toggle_active_true() {
    assert_event("toggle-active-true", |e, parsed| {
        let CallbackEvent::Toggle(t) = e else {
            panic!("expected toggle");
        };
        assert_eq!(t.value, parsed["value"].as_bool().unwrap());
    });
}

#[test]
fn event_toggle_active_false() {
    assert_event("toggle-active-false", |e, parsed| {
        let CallbackEvent::Toggle(t) = e else {
            panic!("expected toggle");
        };
        assert_eq!(t.value, parsed["value"].as_bool().unwrap());
    });
}

#[test]
fn event_toggle_via_value_true() {
    assert_event("toggle-via-value-true", |e, parsed| {
        let CallbackEvent::Toggle(t) = e else {
            panic!("expected toggle");
        };
        assert_eq!(t.value, parsed["value"].as_bool().unwrap());
    });
}

#[test]
fn event_toggle_numeric_value_is_false() {
    assert_event("toggle-numeric-value-is-false", |e, _parsed| {
        let CallbackEvent::Toggle(t) = e else {
            panic!("expected toggle");
        };
        assert_eq!(t.value, false);
    });
}

#[test]
fn event_change_scale() {
    assert_event("change-scale", |e, parsed| {
        let CallbackEvent::Change(c) = e else {
            panic!("expected change");
        };
        assert_eq!(c.id, parsed["id"].as_str().unwrap());
        assert_eq!(c.value, Some(parsed["value"].clone()));
    });
}

#[test]
fn event_change_dropdown() {
    assert_event("change-dropdown", |e, parsed| {
        let CallbackEvent::Change(c) = e else {
            panic!("expected change");
        };
        assert_eq!(c.id, parsed["id"].as_str().unwrap());
        assert_eq!(c.value, Some(parsed["value"].clone()));
    });
}

#[test]
fn event_popover_open() {
    assert_event("popover-open", |e, parsed| {
        let CallbackEvent::Popover(p) = e else {
            panic!("expected popover");
        };
        assert_eq!(p.open, parsed["open"].as_bool().unwrap());
    });
}

#[test]
fn event_popover_close() {
    assert_event("popover-close", |e, parsed| {
        let CallbackEvent::Popover(p) = e else {
            panic!("expected popover");
        };
        assert_eq!(p.open, parsed["open"].as_bool().unwrap());
    });
}
