use crate::model::app_state::AppState;
use crate::model::shared::inspector_tab::InspectorTab;
use crate::model::shared::viewport::{calculate_next_column_offset, calculate_prev_column_offset};
use crate::services::AppServices;
use crate::update::action::{Action, ScrollAmount, ScrollDirection, ScrollTarget};
use crate::update::dispatch_result::DispatchResult;

use super::inspector_max_scroll;

fn inspector_page_scroll_delta(
    state: &AppState,
    services: &AppServices,
    amount: ScrollAmount,
) -> Option<usize> {
    let visible = match services
        .db_capabilities
        .normalize_inspector_tab(state.ui.inspector_tab)
    {
        InspectorTab::Ddl => state.inspector_ddl_visible_rows(),
        _ => state.inspector_visible_rows(),
    };

    amount.page_delta(visible)
}

pub fn reduce_inspector(
    state: &mut AppState,
    action: &Action,
    services: &AppServices,
) -> DispatchResult {
    match action {
        Action::Scroll {
            target: ScrollTarget::Inspector,
            direction: direction @ (ScrollDirection::Up | ScrollDirection::Down),
            amount: ScrollAmount::Line,
        } => {
            let max = inspector_max_scroll(state, services);
            state.ui.inspector_scroll_offset =
                direction.clamp_vertical_offset(state.ui.inspector_scroll_offset, max, 1);
            DispatchResult::handled()
        }
        Action::Scroll {
            target: ScrollTarget::Inspector,
            direction: ScrollDirection::Up,
            amount: ScrollAmount::ToStart,
        } => {
            state.ui.inspector_scroll_offset = 0;
            DispatchResult::handled()
        }
        Action::Scroll {
            target: ScrollTarget::Inspector,
            direction: ScrollDirection::Down,
            amount: ScrollAmount::ToEnd,
        } => {
            state.ui.inspector_scroll_offset = inspector_max_scroll(state, services);
            DispatchResult::handled()
        }
        Action::Scroll {
            target: ScrollTarget::Inspector,
            direction,
            amount: amount @ (ScrollAmount::HalfPage | ScrollAmount::FullPage),
        } => {
            if let Some(delta) = inspector_page_scroll_delta(state, services, *amount) {
                let max = inspector_max_scroll(state, services);
                state.ui.inspector_scroll_offset =
                    direction.clamp_vertical_offset(state.ui.inspector_scroll_offset, max, delta);
            }
            DispatchResult::handled()
        }
        Action::Scroll {
            target: ScrollTarget::Inspector,
            direction: ScrollDirection::Left,
            amount: ScrollAmount::Line,
        } => {
            state.ui.inspector_horizontal_offset =
                calculate_prev_column_offset(state.ui.inspector_horizontal_offset);
            DispatchResult::handled()
        }
        Action::Scroll {
            target: ScrollTarget::Inspector,
            direction: ScrollDirection::Right,
            amount: ScrollAmount::Line,
        } => {
            state.ui.inspector_horizontal_offset = calculate_next_column_offset(
                state.ui.inspector_horizontal_offset,
                state.ui.inspector_viewport_plan.max_offset,
            );
            DispatchResult::handled()
        }

        _ => DispatchResult::pass(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::{Column, ColumnAttributes, Table};
    use crate::model::shared::db_capabilities::DbCapabilities;
    use crate::update::browse::navigation::dispatch_navigation;
    use std::time::Instant;

    mod inspector_scroll_top_bottom {
        use super::*;

        fn state_with_table_detail(columns: usize) -> AppState {
            let mut state = AppState::new("test".to_string());
            state.ui.inspector_pane_height = 10;
            state.ui.inspector_tab = InspectorTab::Columns;
            let cols: Vec<Column> = (0..columns)
                .map(|i| Column {
                    name: format!("col_{i}"),
                    data_type: "text".to_string(),
                    default: None,
                    attributes: ColumnAttributes::empty(),
                    comment: None,
                    ordinal_position: i as i32,
                })
                .collect();
            state.session.set_table_detail_raw(Some(Table {
                schema: "public".to_string(),
                name: "test_table".to_string(),
                owner: None,
                columns: cols,
                primary_key: None,
                indexes: vec![],
                foreign_keys: vec![],
                rls: None,
                triggers: vec![],
                row_count_estimate: Some(0),
                comment: None,
            }));
            state
        }

        #[test]
        fn inspector_scroll_top_resets_to_zero() {
            let mut state = state_with_table_detail(20);
            state.ui.inspector_scroll_offset = 10;

            let effects = dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Inspector,
                    direction: ScrollDirection::Up,
                    amount: ScrollAmount::ToStart,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.inspector_scroll_offset, 0);
        }

        #[test]
        fn inspector_scroll_bottom_goes_to_max() {
            let mut state = state_with_table_detail(20);
            state.ui.inspector_scroll_offset = 0;
            let visible = state.inspector_visible_rows();
            let expected_max = 20_usize.saturating_sub(visible);

            let effects = dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Inspector,
                    direction: ScrollDirection::Down,
                    amount: ScrollAmount::ToEnd,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.inspector_scroll_offset, expected_max);
        }

        #[test]
        fn inspector_scroll_bottom_no_detail_stays_zero() {
            let mut state = AppState::new("test".to_string());
            state.ui.inspector_pane_height = 10;

            let effects = dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Inspector,
                    direction: ScrollDirection::Down,
                    amount: ScrollAmount::ToEnd,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.inspector_scroll_offset, 0);
        }

        #[test]
        fn inspector_half_page_scroll_advances_by_half_visible_rows() {
            let mut state = state_with_table_detail(20);
            state.ui.inspector_scroll_offset = 1;

            let effects = dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Inspector,
                    direction: ScrollDirection::Down,
                    amount: ScrollAmount::HalfPage,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.inspector_scroll_offset, 3);
        }

        #[test]
        fn inspector_full_page_scroll_clamps_to_max() {
            let mut state = state_with_table_detail(20);
            state.ui.inspector_scroll_offset = 12;

            let effects = dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Inspector,
                    direction: ScrollDirection::Down,
                    amount: ScrollAmount::FullPage,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.inspector_scroll_offset, 15);
        }

        #[test]
        fn inspector_page_scroll_stays_zero_when_content_fits_viewport() {
            let mut state = state_with_table_detail(4);

            let effects = dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Inspector,
                    direction: ScrollDirection::Down,
                    amount: ScrollAmount::FullPage,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.inspector_scroll_offset, 0);
        }

        fn services_without_ddl() -> AppServices {
            let mut services = AppServices::stub();
            services.db_capabilities =
                DbCapabilities::new(true, vec![InspectorTab::Info, InspectorTab::Columns]);
            services
        }

        #[test]
        fn inspector_half_page_scroll_normalizes_unsupported_ddl_tab() {
            let mut state = state_with_table_detail(20);
            let services = services_without_ddl();
            state.ui.inspector_pane_height = 7;
            state.ui.inspector_tab = InspectorTab::Ddl;
            state.ui.inspector_scroll_offset = 1;
            let expected_delta = ScrollAmount::HalfPage
                .page_delta(state.inspector_visible_rows())
                .unwrap();

            let effects = dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Inspector,
                    direction: ScrollDirection::Down,
                    amount: ScrollAmount::HalfPage,
                },
                &services,
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.inspector_scroll_offset, 1 + expected_delta);
        }

        #[test]
        fn inspector_full_page_scroll_normalizes_unsupported_ddl_tab() {
            let mut state = state_with_table_detail(20);
            let services = services_without_ddl();
            state.ui.inspector_pane_height = 7;
            state.ui.inspector_tab = InspectorTab::Ddl;
            state.ui.inspector_scroll_offset = 1;

            let effects = dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Inspector,
                    direction: ScrollDirection::Down,
                    amount: ScrollAmount::FullPage,
                },
                &services,
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.inspector_scroll_offset, 3);
        }
    }
}
