use std::time::Instant;

use crate::app::action::{Action, ConnectionTarget, CursorMove};
use crate::app::connection_setup_state::{CONNECTION_INPUT_VISIBLE_WIDTH, ConnectionField};
use crate::app::connection_state::ConnectionState;
use crate::app::effect::Effect;
use crate::app::input_mode::InputMode;
use crate::app::reducers::{
    insert_char_at_cursor, insert_str_at_cursor, validate_all, validate_field,
};
use crate::app::state::AppState;
use crate::domain::MetadataState;
use crate::domain::connection::SslMode;

pub fn reduce(state: &mut AppState, action: &Action, now: Instant) -> Option<Vec<Effect>> {
    match action {
        Action::OpenConnectionSetup => {
            state.connection_setup.reset();
            if !state.connections().is_empty() || state.session.dsn.is_some() {
                state.connection_setup.is_first_run = false;
            }
            state.modal.set_mode(InputMode::ConnectionSetup);
            Some(vec![])
        }
        Action::StartEditConnection(id) => {
            Some(vec![Effect::LoadConnectionForEdit { id: id.clone() }])
        }
        Action::ConnectionEditLoaded(profile) => {
            state.connection_setup =
                crate::app::connection_setup_state::ConnectionSetupState::from_profile(profile);
            state.modal.set_mode(InputMode::ConnectionSetup);
            Some(vec![])
        }
        Action::ConnectionEditLoadFailed(msg) => {
            state.messages.set_error_at(msg.clone(), now);
            Some(vec![])
        }
        Action::CloseConnectionSetup => {
            state.modal.set_mode(InputMode::Normal);
            Some(vec![])
        }

        // ===== Clipboard Paste =====
        Action::Paste(text) if state.modal.active_mode() == InputMode::ConnectionSetup => {
            let clean: String = text.chars().filter(|c| *c != '\n' && *c != '\r').collect();
            let setup = &mut state.connection_setup;
            match setup.focused_field {
                ConnectionField::Port => {
                    let current_len = setup.port.chars().count();
                    let remaining = 5usize.saturating_sub(current_len);
                    let digits: String = clean
                        .chars()
                        .filter(|c| c.is_ascii_digit())
                        .take(remaining)
                        .collect();
                    if !digits.is_empty() {
                        let inserted =
                            insert_str_at_cursor(&mut setup.port, setup.cursor_position, &digits);
                        let new_cursor = setup.cursor_position + inserted;
                        setup.update_cursor(new_cursor, CONNECTION_INPUT_VISIBLE_WIDTH);
                    }
                }
                ConnectionField::SslMode => {}
                _ => {
                    let field_str = match setup.focused_field {
                        ConnectionField::Name => &mut setup.name,
                        ConnectionField::Host => &mut setup.host,
                        ConnectionField::Database => &mut setup.database,
                        ConnectionField::User => &mut setup.user,
                        ConnectionField::Password => &mut setup.password,
                        _ => unreachable!(),
                    };
                    let inserted = insert_str_at_cursor(field_str, setup.cursor_position, &clean);
                    let new_cursor = setup.cursor_position + inserted;
                    setup.update_cursor(new_cursor, CONNECTION_INPUT_VISIBLE_WIDTH);
                }
            }
            Some(vec![])
        }

        // ===== Connection Setup Form =====
        Action::ConnectionSetupInput(c) => {
            let setup = &mut state.connection_setup;
            match setup.focused_field {
                ConnectionField::Name => {
                    insert_char_at_cursor(&mut setup.name, setup.cursor_position, *c);
                    let new_cursor = setup.cursor_position + 1;
                    setup.update_cursor(new_cursor, CONNECTION_INPUT_VISIBLE_WIDTH);
                }
                ConnectionField::Host => {
                    insert_char_at_cursor(&mut setup.host, setup.cursor_position, *c);
                    let new_cursor = setup.cursor_position + 1;
                    setup.update_cursor(new_cursor, CONNECTION_INPUT_VISIBLE_WIDTH);
                }
                ConnectionField::Port => {
                    if c.is_ascii_digit() && setup.port.chars().count() < 5 {
                        insert_char_at_cursor(&mut setup.port, setup.cursor_position, *c);
                        let new_cursor = setup.cursor_position + 1;
                        setup.update_cursor(new_cursor, CONNECTION_INPUT_VISIBLE_WIDTH);
                    }
                }
                ConnectionField::Database => {
                    insert_char_at_cursor(&mut setup.database, setup.cursor_position, *c);
                    let new_cursor = setup.cursor_position + 1;
                    setup.update_cursor(new_cursor, CONNECTION_INPUT_VISIBLE_WIDTH);
                }
                ConnectionField::User => {
                    insert_char_at_cursor(&mut setup.user, setup.cursor_position, *c);
                    let new_cursor = setup.cursor_position + 1;
                    setup.update_cursor(new_cursor, CONNECTION_INPUT_VISIBLE_WIDTH);
                }
                ConnectionField::Password => {
                    insert_char_at_cursor(&mut setup.password, setup.cursor_position, *c);
                    let new_cursor = setup.cursor_position + 1;
                    setup.update_cursor(new_cursor, CONNECTION_INPUT_VISIBLE_WIDTH);
                }
                ConnectionField::SslMode => {}
            }
            Some(vec![])
        }
        Action::ConnectionSetupBackspace => {
            let setup = &mut state.connection_setup;
            if setup.cursor_position == 0 {
                return Some(vec![]);
            }
            let field_str = match setup.focused_field {
                ConnectionField::Name => &mut setup.name,
                ConnectionField::Host => &mut setup.host,
                ConnectionField::Port => &mut setup.port,
                ConnectionField::Database => &mut setup.database,
                ConnectionField::User => &mut setup.user,
                ConnectionField::Password => &mut setup.password,
                ConnectionField::SslMode => return Some(vec![]),
            };
            let char_pos = setup.cursor_position - 1;
            if let Some((byte_idx, _)) = field_str.char_indices().nth(char_pos) {
                field_str.remove(byte_idx);
                setup.update_cursor(char_pos, CONNECTION_INPUT_VISIBLE_WIDTH);
            }
            Some(vec![])
        }
        Action::ConnectionSetupMoveCursor(movement) => {
            let setup = &mut state.connection_setup;
            let field_str = match setup.focused_field {
                ConnectionField::Name => &setup.name,
                ConnectionField::Host => &setup.host,
                ConnectionField::Port => &setup.port,
                ConnectionField::Database => &setup.database,
                ConnectionField::User => &setup.user,
                ConnectionField::Password => &setup.password,
                ConnectionField::SslMode => return Some(vec![]),
            };
            let len = field_str.chars().count();
            let new_pos = match movement {
                CursorMove::Left => setup.cursor_position.saturating_sub(1),
                CursorMove::Right => (setup.cursor_position + 1).min(len),
                CursorMove::Home => 0,
                CursorMove::End => len,
                CursorMove::Up | CursorMove::Down => return Some(vec![]),
            };
            setup.update_cursor(new_pos, CONNECTION_INPUT_VISIBLE_WIDTH);
            Some(vec![])
        }
        Action::ConnectionSetupNextField => {
            let setup = &mut state.connection_setup;
            validate_field(setup, setup.focused_field);
            if let Some(next) = setup.focused_field.next() {
                setup.focused_field = next;
                setup.cursor_to_end();
            }
            Some(vec![])
        }
        Action::ConnectionSetupPrevField => {
            let setup = &mut state.connection_setup;
            validate_field(setup, setup.focused_field);
            if let Some(prev) = setup.focused_field.prev() {
                setup.focused_field = prev;
                setup.cursor_to_end();
            }
            Some(vec![])
        }
        Action::ConnectionSetupToggleDropdown => {
            let setup = &mut state.connection_setup;
            if setup.focused_field == ConnectionField::SslMode {
                setup.ssl_dropdown.is_open = !setup.ssl_dropdown.is_open;
                if setup.ssl_dropdown.is_open {
                    setup.ssl_dropdown.selected_index = SslMode::all_variants()
                        .iter()
                        .position(|v| *v == setup.ssl_mode)
                        .unwrap_or(2);
                }
            }
            Some(vec![])
        }
        Action::ConnectionSetupDropdownNext => {
            let setup = &mut state.connection_setup;
            if setup.ssl_dropdown.is_open {
                let max = SslMode::all_variants().len() - 1;
                if setup.ssl_dropdown.selected_index < max {
                    setup.ssl_dropdown.selected_index += 1;
                }
            }
            Some(vec![])
        }
        Action::ConnectionSetupDropdownPrev => {
            let setup = &mut state.connection_setup;
            if setup.ssl_dropdown.is_open {
                setup.ssl_dropdown.selected_index =
                    setup.ssl_dropdown.selected_index.saturating_sub(1);
            }
            Some(vec![])
        }
        Action::ConnectionSetupDropdownConfirm => {
            let setup = &mut state.connection_setup;
            if setup.ssl_dropdown.is_open {
                if let Some(mode) = SslMode::all_variants().get(setup.ssl_dropdown.selected_index) {
                    setup.ssl_mode = *mode;
                }
                setup.ssl_dropdown.is_open = false;
            }
            Some(vec![])
        }
        Action::ConnectionSetupDropdownCancel => {
            state.connection_setup.ssl_dropdown.is_open = false;
            Some(vec![])
        }
        Action::ConnectionSetupSave => {
            let setup = &mut state.connection_setup;
            validate_all(setup);
            if setup.validation_errors.is_empty() {
                let port = setup.port.parse().unwrap_or(5432);
                state
                    .session
                    .set_connection_state(ConnectionState::Connecting);
                state.session.set_metadata_state(MetadataState::Loading);
                Some(vec![Effect::SaveAndConnect {
                    id: setup.editing_id.clone(),
                    name: setup.name.clone(),
                    host: setup.host.clone(),
                    port,
                    database: setup.database.clone(),
                    user: setup.user.clone(),
                    password: setup.password.clone(),
                    ssl_mode: setup.ssl_mode,
                }])
            } else {
                Some(vec![])
            }
        }
        Action::ConnectionSetupCancel => {
            if state.connection_setup.is_first_run {
                state.confirm_dialog.open(
                    "Confirm",
                    "No connection configured.\nAre you sure you want to quit?",
                    crate::app::confirm_dialog_state::ConfirmIntent::QuitNoConnection,
                );
                state.modal.push_mode(InputMode::ConfirmDialog);
                Some(vec![])
            } else {
                state.modal.set_mode(InputMode::Normal);
                Some(vec![Effect::DispatchActions(vec![Action::TryConnect])])
            }
        }
        Action::ConnectionSaveCompleted(ConnectionTarget { id, dsn, name }) => {
            state.connection_setup.is_first_run = false;
            state.modal.set_mode(InputMode::Normal);
            state.session.active_connection_id = Some(id.clone());
            state.session.active_connection_name = Some(name.clone());
            state.session.read_only = false;
            state.session.begin_connecting(dsn);
            Some(vec![Effect::FetchMetadata { dsn: dsn.clone() }])
        }
        Action::ConnectionSaveFailed(msg) => {
            state
                .session
                .set_connection_state(ConnectionState::NotConnected);
            state.session.set_metadata_state(MetadataState::NotLoaded);
            state.messages.set_error_at(msg.clone(), now);
            Some(vec![])
        }

        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::connection::ConnectionProfile;

    fn create_profile(name: &str) -> ConnectionProfile {
        ConnectionProfile::new(
            name.to_string(),
            "localhost".to_string(),
            5432,
            "db".to_string(),
            "user".to_string(),
            "pass".to_string(),
            Default::default(),
        )
        .unwrap()
    }

    mod paste {
        use super::*;
        use crate::app::connection_setup_state::ConnectionField;

        fn setup_state_with_field(field: ConnectionField) -> AppState {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::ConnectionSetup);
            state.connection_setup.focused_field = field;
            state.connection_setup.cursor_position = 0;
            // Clear default values so tests start clean
            state.connection_setup.host.clear();
            state.connection_setup.port.clear();
            state.connection_setup.database.clear();
            state.connection_setup.user.clear();
            state.connection_setup.name.clear();
            state.connection_setup.password.clear();
            state
        }

        #[test]
        fn paste_into_host_inserts_text() {
            let mut state = setup_state_with_field(ConnectionField::Host);

            reduce(
                &mut state,
                &Action::Paste("db.example.com".to_string()),
                Instant::now(),
            );

            assert_eq!(state.connection_setup.host, "db.example.com");
        }

        #[test]
        fn paste_into_port_filters_non_digits() {
            let mut state = setup_state_with_field(ConnectionField::Port);

            reduce(
                &mut state,
                &Action::Paste("54ab32".to_string()),
                Instant::now(),
            );

            assert_eq!(state.connection_setup.port, "5432");
        }

        #[test]
        fn paste_into_port_respects_limit() {
            let mut state = setup_state_with_field(ConnectionField::Port);
            state.connection_setup.port = "54".to_string();
            state.connection_setup.cursor_position = 2;

            reduce(
                &mut state,
                &Action::Paste("321000".to_string()),
                Instant::now(),
            );

            assert_eq!(state.connection_setup.port, "54321");
        }

        #[test]
        fn paste_into_full_port_does_nothing() {
            let mut state = setup_state_with_field(ConnectionField::Port);
            state.connection_setup.port = "12345".to_string();
            state.connection_setup.cursor_position = 5;

            reduce(&mut state, &Action::Paste("6".to_string()), Instant::now());

            assert_eq!(state.connection_setup.port, "12345");
        }

        #[test]
        fn paste_strips_newlines() {
            let mut state = setup_state_with_field(ConnectionField::Host);

            reduce(
                &mut state,
                &Action::Paste("local\nhost".to_string()),
                Instant::now(),
            );

            assert_eq!(state.connection_setup.host, "localhost");
        }

        #[test]
        fn paste_into_ssl_mode_ignored() {
            let mut state = setup_state_with_field(ConnectionField::SslMode);
            let ssl_mode_before = state.connection_setup.ssl_mode;

            reduce(
                &mut state,
                &Action::Paste("disable".to_string()),
                Instant::now(),
            );

            assert_eq!(state.connection_setup.ssl_mode, ssl_mode_before);
        }

        #[test]
        fn paste_updates_cursor_and_viewport() {
            let mut state = setup_state_with_field(ConnectionField::Host);

            reduce(
                &mut state,
                &Action::Paste("db.example.com".to_string()),
                Instant::now(),
            );

            assert_eq!(state.connection_setup.cursor_position, 14);
        }
    }

    mod connection_save {
        use super::*;
        use crate::app::action::ConnectionTarget;
        use crate::app::connection_state::ConnectionState;
        use crate::domain::MetadataState;

        fn fill_valid_form(state: &mut AppState) {
            state.connection_setup.name = "test".to_string();
            state.connection_setup.host = "localhost".to_string();
            state.connection_setup.port = "5432".to_string();
            state.connection_setup.database = "db".to_string();
            state.connection_setup.user = "user".to_string();
            state.connection_setup.password = "pass".to_string();
        }

        #[test]
        fn save_sets_connection_and_metadata_state_as_pair() {
            let mut state = AppState::new("test".to_string());
            fill_valid_form(&mut state);

            reduce(&mut state, &Action::ConnectionSetupSave, Instant::now());

            assert_eq!(
                state.session.connection_state(),
                ConnectionState::Connecting
            );
            assert_eq!(state.session.metadata_state(), &MetadataState::Loading);
        }

        #[test]
        fn save_completed_resets_read_only() {
            let mut state = AppState::new("test".to_string());
            state.session.read_only = true;

            let action = Action::ConnectionSaveCompleted(ConnectionTarget {
                id: crate::domain::ConnectionId::new(),
                dsn: "postgres://localhost/new_db".to_string(),
                name: "new_db".to_string(),
            });
            reduce(&mut state, &action, Instant::now());

            assert!(!state.session.read_only);
        }
    }

    mod open_connection_setup {
        use super::*;

        #[test]
        fn is_first_run_true_when_no_connections() {
            let mut state = AppState::new("test".to_string());

            reduce(&mut state, &Action::OpenConnectionSetup, Instant::now());

            assert!(state.connection_setup.is_first_run);
        }

        #[test]
        fn is_first_run_false_when_connections_exist() {
            let mut state = AppState::new("test".to_string());
            let profile = create_profile("test");
            state.set_connections(vec![profile]);

            reduce(&mut state, &Action::OpenConnectionSetup, Instant::now());

            assert!(!state.connection_setup.is_first_run);
        }

        #[test]
        fn is_first_run_false_when_already_connected() {
            let mut state = AppState::new("test".to_string());
            state.session.dsn = Some("postgres://localhost/db".to_string());

            reduce(&mut state, &Action::OpenConnectionSetup, Instant::now());

            assert!(!state.connection_setup.is_first_run);
        }
    }
}
