use nalgebra_glm::{Vec2, Vec4};
use winit::keyboard::KeyCode;

use crate::ecs::ui::resources::UiEvent;
use crate::ecs::world::World;

use super::types::{
    ActionRecord, AssertKind, EventMatch, ScheduledAction, TestAction, UiTestResult,
};

pub struct UiTestDriver {
    pub(super) actions: Vec<ScheduledAction>,
    pub(super) frame: u64,
    pub(super) frame_cursor: u64,
    pub(super) results: Vec<UiTestResult>,
    pub(super) captured_events: Vec<UiEvent>,
    pub(super) active: bool,
    pub(super) exit_on_complete: bool,
    pub(super) cursor: usize,
    pub(super) pending_release: bool,
    pub(super) pending_right_release: bool,
    pub(super) pending_key_releases: Vec<KeyCode>,
    pub(super) test_name: String,
    pub(super) json_output_path: Option<String>,
    pub(super) action_records: Vec<ActionRecord>,
    pub(super) screenshots: Vec<String>,
    pub(super) result_frames: Vec<u64>,
}

impl UiTestDriver {
    pub fn new(name: &str) -> Self {
        Self {
            actions: Vec::new(),
            frame: 0,
            frame_cursor: 0,
            results: Vec::new(),
            captured_events: Vec::new(),
            active: false,
            exit_on_complete: true,
            cursor: 0,
            pending_release: false,
            pending_right_release: false,
            pending_key_releases: Vec::new(),
            test_name: name.to_string(),
            json_output_path: None,
            action_records: Vec::new(),
            screenshots: Vec::new(),
            result_frames: Vec::new(),
        }
    }

    pub fn set_exit_on_complete(&mut self, exit: bool) {
        self.exit_on_complete = exit;
    }

    pub fn set_json_output_path(&mut self, path: impl Into<String>) {
        self.json_output_path = Some(path.into());
    }

    pub fn start(&mut self) {
        self.active = true;
        self.frame = 0;
        self.cursor = 0;
        self.results.clear();
        self.captured_events.clear();
        self.actions.sort_by_key(|a| a.frame);
    }

    pub fn is_active(&self) -> bool {
        self.active
    }

    pub fn results(&self) -> &[UiTestResult] {
        &self.results
    }

    pub fn all_passed(&self) -> bool {
        self.results.iter().all(|r| r.passed)
    }

    pub(super) fn schedule(&mut self, frame: u64, action: TestAction) {
        self.actions.push(ScheduledAction { frame, action });
    }

    pub fn at_frame(&mut self, frame: u64) -> ActionBuilder<'_> {
        self.frame_cursor = frame;
        ActionBuilder {
            driver: self,
            frame,
        }
    }

    pub fn wait(&mut self, frames: u64) -> ActionBuilder<'_> {
        self.frame_cursor += frames;
        let frame = self.frame_cursor;
        ActionBuilder {
            driver: self,
            frame,
        }
    }

    pub fn then(&mut self) -> ActionBuilder<'_> {
        let frame = self.frame_cursor;
        ActionBuilder {
            driver: self,
            frame,
        }
    }

    pub fn find_entity(&self, world: &World, test_id: &str) -> Option<freecs::Entity> {
        world
            .resources
            .retained_ui
            .accessibility
            .test_id_map
            .get(test_id)
            .copied()
    }

    pub fn entity_center(&self, world: &World, test_id: &str) -> Option<Vec2> {
        self.resolve_entity_center(world, test_id)
    }

    pub fn record_pass(&mut self, name: &str, message: &str) {
        self.results.push(UiTestResult {
            name: name.to_string(),
            passed: true,
            message: message.to_string(),
        });
    }

    pub fn record_fail(&mut self, name: &str, message: &str) {
        self.results.push(UiTestResult {
            name: name.to_string(),
            passed: false,
            message: message.to_string(),
        });
    }

    pub fn record_assert(&mut self, name: &str, passed: bool, message: &str) {
        self.results.push(UiTestResult {
            name: name.to_string(),
            passed,
            message: message.to_string(),
        });
    }
}

pub struct ActionBuilder<'a> {
    driver: &'a mut UiTestDriver,
    frame: u64,
}

impl ActionBuilder<'_> {
    pub fn move_mouse(self, position: Vec2) -> Self {
        self.driver
            .schedule(self.frame, TestAction::MoveMouse(position));
        self
    }

    pub fn move_mouse_to(self, test_id: &str) -> Self {
        self.driver.schedule(
            self.frame,
            TestAction::MoveMouseToEntity(test_id.to_string()),
        );
        self
    }

    pub fn click(self, position: Vec2) -> Self {
        self.driver
            .schedule(self.frame, TestAction::ClickAt(position));
        self
    }

    pub fn click_entity(self, test_id: &str) -> Self {
        self.driver
            .schedule(self.frame, TestAction::ClickAtEntity(test_id.to_string()));
        self
    }

    pub fn right_click(self, position: Vec2) -> Self {
        self.driver
            .schedule(self.frame, TestAction::RightClickAt(position));
        self
    }

    pub fn right_click_entity(self, test_id: &str) -> Self {
        self.driver.schedule(
            self.frame,
            TestAction::RightClickAtEntity(test_id.to_string()),
        );
        self
    }

    pub fn drag_entity(self, test_id: &str, end_position: Vec2) -> Self {
        self.driver.schedule(
            self.frame,
            TestAction::DragEntity(test_id.to_string(), end_position),
        );
        self
    }

    pub fn set_slider_value(self, test_id: &str, value: f32) -> Self {
        self.driver.schedule(
            self.frame,
            TestAction::SetSliderValue(test_id.to_string(), value),
        );
        self
    }

    pub fn select_dropdown_option(self, test_id: &str, option_index: usize) -> Self {
        self.driver.schedule(
            self.frame,
            TestAction::SelectDropdownOption(test_id.to_string(), option_index),
        );
        self
    }

    pub fn select_tab(self, test_id: &str, tab_index: usize) -> Self {
        self.driver.schedule(
            self.frame,
            TestAction::SelectTab(test_id.to_string(), tab_index),
        );
        self
    }

    pub fn mouse_press(self) -> Self {
        self.driver.schedule(self.frame, TestAction::MousePress);
        self
    }

    pub fn mouse_release(self) -> Self {
        self.driver.schedule(self.frame, TestAction::MouseRelease);
        self
    }

    pub fn right_mouse_press(self) -> Self {
        self.driver
            .schedule(self.frame, TestAction::RightMousePress);
        self
    }

    pub fn right_mouse_release(self) -> Self {
        self.driver
            .schedule(self.frame, TestAction::RightMouseRelease);
        self
    }

    pub fn type_text(self, text: &str) -> Self {
        let chars: Vec<char> = text.chars().collect();
        self.driver
            .schedule(self.frame, TestAction::TypeChars(chars));
        self
    }

    pub fn press_key(self, key: KeyCode) -> Self {
        self.driver
            .schedule(self.frame, TestAction::PressAndReleaseKey(key));
        self
    }

    pub fn key_down(self, key: KeyCode) -> Self {
        self.driver.schedule(self.frame, TestAction::KeyDown(key));
        self
    }

    pub fn key_up(self, key: KeyCode) -> Self {
        self.driver.schedule(self.frame, TestAction::KeyUp(key));
        self
    }

    pub fn scroll(self, delta: Vec2) -> Self {
        self.driver.schedule(self.frame, TestAction::Scroll(delta));
        self
    }

    pub fn screenshot(self, path: &str) -> Self {
        self.driver
            .schedule(self.frame, TestAction::Screenshot(path.to_string()));
        self
    }

    pub fn log(self, message: &str) -> Self {
        self.driver
            .schedule(self.frame, TestAction::Log(message.to_string()));
        self
    }

    pub fn assert_visible(self, test_id: &str) -> Self {
        let name = format!("{test_id} visible");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name, AssertKind::Visible(test_id.to_string())),
        );
        self
    }

    pub fn assert_not_visible(self, test_id: &str) -> Self {
        let name = format!("{test_id} not visible");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name, AssertKind::NotVisible(test_id.to_string())),
        );
        self
    }

    pub fn assert_focused(self, test_id: &str) -> Self {
        let name = format!("{test_id} focused");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name, AssertKind::Focused(test_id.to_string())),
        );
        self
    }

    pub fn assert_not_focused(self, test_id: &str) -> Self {
        let name = format!("{test_id} not focused");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name, AssertKind::NotFocused(test_id.to_string())),
        );
        self
    }

    pub fn assert_hovered(self, test_id: &str) -> Self {
        let name = format!("{test_id} hovered");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name, AssertKind::Hovered(test_id.to_string())),
        );
        self
    }

    pub fn assert_not_hovered(self, test_id: &str) -> Self {
        let name = format!("{test_id} not hovered");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name, AssertKind::NotHovered(test_id.to_string())),
        );
        self
    }

    pub fn assert_disabled(self, test_id: &str) -> Self {
        let name = format!("{test_id} disabled");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name, AssertKind::Disabled(test_id.to_string())),
        );
        self
    }

    pub fn assert_not_disabled(self, test_id: &str) -> Self {
        let name = format!("{test_id} enabled");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name, AssertKind::NotDisabled(test_id.to_string())),
        );
        self
    }

    pub fn assert_text(self, test_id: &str, expected: &str) -> Self {
        let name = format!("{test_id} text");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(
                name,
                AssertKind::TextEquals(test_id.to_string(), expected.to_string()),
            ),
        );
        self
    }

    pub fn assert_value_f32(self, test_id: &str, expected: f32) -> Self {
        let name = format!("{test_id} value");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(
                name,
                AssertKind::WidgetValueF32(test_id.to_string(), expected, 0.01),
            ),
        );
        self
    }

    pub fn assert_value_f32_approx(self, test_id: &str, expected: f32, tolerance: f32) -> Self {
        let name = format!("{test_id} value (approx)");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(
                name,
                AssertKind::WidgetValueF32(test_id.to_string(), expected, tolerance),
            ),
        );
        self
    }

    pub fn assert_value_bool(self, test_id: &str, expected: bool) -> Self {
        let name = format!("{test_id} value");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(
                name,
                AssertKind::WidgetValueBool(test_id.to_string(), expected),
            ),
        );
        self
    }

    pub fn assert_value_usize(self, test_id: &str, expected: usize) -> Self {
        let name = format!("{test_id} value");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(
                name,
                AssertKind::WidgetValueUsize(test_id.to_string(), expected),
            ),
        );
        self
    }

    pub fn assert_value_string(self, test_id: &str, expected: &str) -> Self {
        let name = format!("{test_id} value");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(
                name,
                AssertKind::WidgetValueString(test_id.to_string(), expected.to_string()),
            ),
        );
        self
    }

    pub fn assert_value_vec4(self, test_id: &str, expected: Vec4, tolerance: f32) -> Self {
        let name = format!("{test_id} value");
        self.driver.schedule(
            self.frame,
            TestAction::Assert(
                name,
                AssertKind::WidgetValueVec4(test_id.to_string(), expected, tolerance),
            ),
        );
        self
    }

    fn assert_event(self, name: &str, event_match: EventMatch) -> Self {
        self.driver.schedule(
            self.frame,
            TestAction::Assert(name.to_string(), AssertKind::EventFired(event_match)),
        );
        self
    }

    pub fn assert_event_fired_button_clicked(self) -> Self {
        self.assert_event("button clicked event", EventMatch::ButtonClicked)
    }

    pub fn assert_event_fired_slider_changed(self) -> Self {
        self.assert_event("slider changed event", EventMatch::SliderChanged)
    }

    pub fn assert_event_fired_toggle_changed(self) -> Self {
        self.assert_event("toggle changed event", EventMatch::ToggleChanged)
    }

    pub fn assert_event_fired_text_submitted(self) -> Self {
        self.assert_event("text input submitted event", EventMatch::TextInputSubmitted)
    }

    pub fn assert_event_fired_text_changed(self) -> Self {
        self.assert_event("text input changed event", EventMatch::TextInputChanged)
    }

    pub fn assert_event_fired_checkbox_changed(self) -> Self {
        self.assert_event("checkbox changed event", EventMatch::CheckboxChanged)
    }

    pub fn assert_event_fired_dropdown_changed(self) -> Self {
        self.assert_event("dropdown changed event", EventMatch::DropdownChanged)
    }

    pub fn assert_event_fired_radio_changed(self) -> Self {
        self.assert_event("radio changed event", EventMatch::RadioChanged)
    }

    pub fn assert_event_fired_tab_changed(self) -> Self {
        self.assert_event("tab changed event", EventMatch::TabChanged)
    }

    pub fn assert_event_fired_drag_value_changed(self) -> Self {
        self.assert_event("drag value changed event", EventMatch::DragValueChanged)
    }

    pub fn assert_event_fired_menu_item_clicked(self) -> Self {
        self.assert_event("menu item clicked event", EventMatch::MenuItemClicked)
    }

    pub fn assert_event_fired_color_picker_changed(self) -> Self {
        self.assert_event("color picker changed event", EventMatch::ColorPickerChanged)
    }

    pub fn assert_event_fired_selectable_label_clicked(self) -> Self {
        self.assert_event(
            "selectable label clicked event",
            EventMatch::SelectableLabelClicked,
        )
    }

    pub fn assert_event_fired_context_menu_item_clicked(self) -> Self {
        self.assert_event(
            "context menu item clicked event",
            EventMatch::ContextMenuItemClicked,
        )
    }

    pub fn assert_event_fired_tree_node_selected(self) -> Self {
        self.assert_event("tree node selected event", EventMatch::TreeNodeSelected)
    }

    pub fn assert_event_fired_tree_node_toggled(self) -> Self {
        self.assert_event("tree node toggled event", EventMatch::TreeNodeToggled)
    }

    pub fn assert_event_fired_modal_closed(self) -> Self {
        self.assert_event("modal closed event", EventMatch::ModalClosed)
    }

    pub fn assert_event_fired_command_palette_executed(self) -> Self {
        self.assert_event(
            "command palette executed event",
            EventMatch::CommandPaletteExecuted,
        )
    }

    pub fn assert_event_fired_tile_tab_activated(self) -> Self {
        self.assert_event("tile tab activated event", EventMatch::TileTabActivated)
    }

    pub fn assert_event_fired_tile_tab_closed(self) -> Self {
        self.assert_event("tile tab closed event", EventMatch::TileTabClosed)
    }

    pub fn assert_event_fired_tile_splitter_moved(self) -> Self {
        self.assert_event("tile splitter moved event", EventMatch::TileSplitterMoved)
    }

    pub fn assert_event_fired_canvas_clicked(self) -> Self {
        self.assert_event("canvas clicked event", EventMatch::CanvasClicked)
    }

    pub fn assert_event_fired_virtual_list_item_clicked(self) -> Self {
        self.assert_event(
            "virtual list item clicked event",
            EventMatch::VirtualListItemClicked,
        )
    }

    pub fn assert_event_fired_text_area_changed(self) -> Self {
        self.assert_event("text area changed event", EventMatch::TextAreaChanged)
    }

    pub fn assert_event_fired_rich_text_editor_changed(self) -> Self {
        self.assert_event(
            "rich text editor changed event",
            EventMatch::RichTextEditorChanged,
        )
    }

    pub fn assert_event_fired_data_grid_filter_changed(self) -> Self {
        self.assert_event(
            "data grid filter changed event",
            EventMatch::DataGridFilterChanged,
        )
    }

    pub fn assert_event_fired_range_slider_changed(self) -> Self {
        self.assert_event("range slider changed event", EventMatch::RangeSliderChanged)
    }

    pub fn assert_event_fired_data_grid_cell_edited(self) -> Self {
        self.assert_event(
            "data grid cell edited event",
            EventMatch::DataGridCellEdited,
        )
    }

    pub fn assert_event_fired_breadcrumb_clicked(self) -> Self {
        self.assert_event("breadcrumb clicked event", EventMatch::BreadcrumbClicked)
    }

    pub fn assert_event_fired_splitter_changed(self) -> Self {
        self.assert_event("splitter changed event", EventMatch::SplitterChanged)
    }

    pub fn assert_event_fired_multi_select_changed(self) -> Self {
        self.assert_event("multi select changed event", EventMatch::MultiSelectChanged)
    }

    pub fn assert_event_fired_date_picker_changed(self) -> Self {
        self.assert_event("date picker changed event", EventMatch::DatePickerChanged)
    }

    pub fn assert_event_fired_drag_started(self) -> Self {
        self.assert_event("drag started event", EventMatch::DragStarted)
    }

    pub fn assert_event_fired_drag_dropped(self) -> Self {
        self.assert_event("drag dropped event", EventMatch::DragDropped)
    }

    pub fn assert_event_fired_drag_cancelled(self) -> Self {
        self.assert_event("drag cancelled event", EventMatch::DragCancelled)
    }

    pub fn exit_pass(self) -> Self {
        self.driver.schedule(self.frame, TestAction::Exit(true));
        self
    }

    pub fn exit_fail(self) -> Self {
        self.driver.schedule(self.frame, TestAction::Exit(false));
        self
    }
}

pub fn setup_test_scene(world: &mut World) {
    world.resources.retained_ui.enabled = true;
    world.resources.retained_ui.background_color =
        Some(nalgebra_glm::Vec4::new(0.05, 0.05, 0.08, 1.0));
    let camera =
        crate::ecs::camera::commands::spawn_ortho_camera(world, nalgebra_glm::Vec2::new(0.0, 0.0));
    world.resources.active_camera = Some(camera);
}

pub fn create_test_root(tree: &mut crate::ecs::ui::builder::UiTreeBuilder<'_>) -> freecs::Entity {
    tree.add_node()
        .boundary(
            crate::ecs::ui::units::Ab(Vec2::new(20.0, 20.0)),
            crate::ecs::ui::units::Ab(Vec2::new(-20.0, -20.0))
                + crate::ecs::ui::units::Rl(Vec2::new(100.0, 100.0)),
        )
        .flow(
            crate::ecs::ui::layout_types::FlowDirection::Vertical,
            8.0,
            8.0,
        )
        .entity()
}
