// SPDX-FileCopyrightText: 2025 The superseedr Contributors
// SPDX-License-Identifier: GPL-3.0-or-later

use std::sync::Arc;

use crate::app::{AppCommand, AppMode, ConfigItem, FileBrowserMode};
use crate::config::Settings;
use crate::token_bucket::TokenBucket;
use crate::tui::formatters::{format_limit_bps, path_to_string};
use crate::tui::screen_context::ScreenContext;
use directories::UserDirs;
use ratatui::crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEventKind};
use ratatui::layout::{Alignment, Constraint, Direction, Layout};
use ratatui::prelude::{Frame, Line, Span, Style};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use tokio::sync::mpsc;

#[derive(Clone, Debug, PartialEq)]
pub enum ConfigAction {
    SaveAndExit,
    StartEditOrBrowse,
    MoveUp,
    MoveDown,
    ResetSelected,
    IncreaseSelected,
    DecreaseSelected,
    EditInsert(char),
    EditBackspace,
    EditCancel,
    EditCommit,
}

pub enum ConfigEffect {
    AppCommand(Box<AppCommand>),
    SetDownloadRate(u64),
    SetUploadRate(u64),
    ToNormal,
}

pub struct ConfigHandleContext<'a> {
    pub mode: &'a mut AppMode,
    pub settings_edit: &'a mut Box<Settings>,
    pub selected_index: &'a mut usize,
    pub items: &'a mut [ConfigItem],
    pub editing: &'a mut Option<(ConfigItem, String)>,
    pub app_command_tx: &'a mpsc::Sender<AppCommand>,
    pub global_dl_bucket: &'a Arc<TokenBucket>,
    pub global_ul_bucket: &'a Arc<TokenBucket>,
}

#[derive(Default)]
pub struct ConfigReduceResult {
    pub consumed: bool,
    pub effects: Vec<ConfigEffect>,
}

fn shared_path_is_manual(item: ConfigItem) -> bool {
    crate::config::is_shared_config_mode() && item == ConfigItem::DefaultDownloadFolder
}
fn map_key_to_config_action(
    key_code: KeyCode,
    editing: &Option<(ConfigItem, String)>,
) -> Option<ConfigAction> {
    if editing.is_some() {
        return match key_code {
            KeyCode::Char(c) if c.is_ascii_digit() => Some(ConfigAction::EditInsert(c)),
            KeyCode::Backspace => Some(ConfigAction::EditBackspace),
            KeyCode::Esc => Some(ConfigAction::EditCancel),
            KeyCode::Enter => Some(ConfigAction::EditCommit),
            _ => None,
        };
    }

    match key_code {
        KeyCode::Esc | KeyCode::Char('Q') => Some(ConfigAction::SaveAndExit),
        KeyCode::Enter => Some(ConfigAction::StartEditOrBrowse),
        KeyCode::Up | KeyCode::Char('k') => Some(ConfigAction::MoveUp),
        KeyCode::Down | KeyCode::Char('j') => Some(ConfigAction::MoveDown),
        KeyCode::Char('r') => Some(ConfigAction::ResetSelected),
        KeyCode::Right | KeyCode::Char('l') => Some(ConfigAction::IncreaseSelected),
        KeyCode::Left | KeyCode::Char('h') => Some(ConfigAction::DecreaseSelected),
        _ => None,
    }
}

pub fn reduce_config_action(
    action: ConfigAction,
    settings_edit: &mut Box<Settings>,
    selected_index: &mut usize,
    items: &mut [ConfigItem],
    editing: &mut Option<(ConfigItem, String)>,
) -> ConfigReduceResult {
    let mut result = ConfigReduceResult::default();
    match action {
        ConfigAction::SaveAndExit => {
            result.consumed = true;
            result.effects.push(ConfigEffect::AppCommand(Box::new(
                AppCommand::UpdateConfig(*settings_edit.clone()),
            )));
            result.effects.push(ConfigEffect::ToNormal);
        }
        ConfigAction::StartEditOrBrowse => {
            result.consumed = true;
            let selected_item = items[*selected_index];
            match selected_item {
                ConfigItem::GlobalDownloadLimit
                | ConfigItem::GlobalUploadLimit
                | ConfigItem::ClientPort => {
                    *editing = Some((selected_item, String::new()));
                }
                ConfigItem::DefaultDownloadFolder | ConfigItem::WatchFolder => {
                    if shared_path_is_manual(selected_item) {
                        return result;
                    }
                    let initial_path = if selected_item == ConfigItem::WatchFolder {
                        settings_edit.watch_folder.clone()
                    } else {
                        settings_edit.default_download_folder.clone()
                    }
                    .unwrap_or_else(|| {
                        UserDirs::new()
                            .and_then(|ud| ud.download_dir().map(|p| p.to_path_buf()))
                            .unwrap_or_else(|| std::path::PathBuf::from("."))
                    });

                    result.effects.push(ConfigEffect::AppCommand(Box::new(
                        AppCommand::FetchFileTree {
                            path: initial_path,
                            browser_mode: FileBrowserMode::ConfigPathSelection {
                                target_item: selected_item,
                                current_settings: settings_edit.clone(),
                                selected_index: *selected_index,
                                items: items.to_vec(),
                            },
                            highlight_path: None,
                        },
                    )));
                }
            }
        }
        ConfigAction::MoveUp => {
            result.consumed = true;
            *selected_index = selected_index.saturating_sub(1);
        }
        ConfigAction::MoveDown => {
            result.consumed = true;
            if *selected_index < items.len().saturating_sub(1) {
                *selected_index += 1;
            }
        }
        ConfigAction::ResetSelected => {
            result.consumed = true;
            let default_settings = Settings::default();
            let selected_item = items[*selected_index];
            match selected_item {
                ConfigItem::ClientPort => {
                    settings_edit.client_port = default_settings.client_port;
                }
                ConfigItem::DefaultDownloadFolder => {
                    if !shared_path_is_manual(selected_item) {
                        settings_edit.default_download_folder =
                            default_settings.default_download_folder;
                    }
                }
                ConfigItem::WatchFolder => {
                    settings_edit.watch_folder = default_settings.watch_folder;
                }
                ConfigItem::GlobalDownloadLimit => {
                    settings_edit.global_download_limit_bps =
                        default_settings.global_download_limit_bps;
                }
                ConfigItem::GlobalUploadLimit => {
                    settings_edit.global_upload_limit_bps =
                        default_settings.global_upload_limit_bps;
                }
            }
        }
        ConfigAction::IncreaseSelected => {
            result.consumed = true;
            let item = items[*selected_index];
            let increment = 10_000 * 8;
            match item {
                ConfigItem::GlobalDownloadLimit => {
                    let new_rate = settings_edit
                        .global_download_limit_bps
                        .saturating_add(increment);
                    settings_edit.global_download_limit_bps = new_rate;
                    result.effects.push(ConfigEffect::SetDownloadRate(new_rate));
                }
                ConfigItem::GlobalUploadLimit => {
                    let new_rate = settings_edit
                        .global_upload_limit_bps
                        .saturating_add(increment);
                    settings_edit.global_upload_limit_bps = new_rate;
                    result.effects.push(ConfigEffect::SetUploadRate(new_rate));
                }
                _ => {}
            }
        }
        ConfigAction::DecreaseSelected => {
            result.consumed = true;
            let item = items[*selected_index];
            let decrement = 10_000 * 8;
            match item {
                ConfigItem::GlobalDownloadLimit => {
                    let new_rate = settings_edit
                        .global_download_limit_bps
                        .saturating_sub(decrement);
                    settings_edit.global_download_limit_bps = new_rate;
                    result.effects.push(ConfigEffect::SetDownloadRate(new_rate));
                }
                ConfigItem::GlobalUploadLimit => {
                    let new_rate = settings_edit
                        .global_upload_limit_bps
                        .saturating_sub(decrement);
                    settings_edit.global_upload_limit_bps = new_rate;
                    result.effects.push(ConfigEffect::SetUploadRate(new_rate));
                }
                _ => {}
            }
        }
        ConfigAction::EditInsert(c) => {
            result.consumed = true;
            if let Some((_item, buffer)) = editing {
                buffer.push(c);
            }
        }
        ConfigAction::EditBackspace => {
            result.consumed = true;
            if let Some((_item, buffer)) = editing {
                buffer.pop();
            }
        }
        ConfigAction::EditCancel => {
            result.consumed = true;
            *editing = None;
        }
        ConfigAction::EditCommit => {
            result.consumed = true;
            if let Some((item, buffer)) = editing {
                match item {
                    ConfigItem::ClientPort => {
                        if let Ok(new_port) = buffer.parse::<u16>() {
                            if new_port > 0 {
                                settings_edit.client_port = new_port;
                            }
                        }
                    }
                    ConfigItem::GlobalDownloadLimit => {
                        if let Ok(new_rate) = buffer.parse::<u64>() {
                            settings_edit.global_download_limit_bps = new_rate;
                            result.effects.push(ConfigEffect::SetDownloadRate(new_rate));
                        }
                    }
                    ConfigItem::GlobalUploadLimit => {
                        if let Ok(new_rate) = buffer.parse::<u64>() {
                            settings_edit.global_upload_limit_bps = new_rate;
                            result.effects.push(ConfigEffect::SetUploadRate(new_rate));
                        }
                    }
                    _ => {}
                }
                *editing = None;
            }
        }
    }
    result
}

pub fn draw(
    f: &mut Frame,
    screen: &ScreenContext<'_>,
    settings: &Settings,
    selected_index: usize,
    items: &[ConfigItem],
    editing: &Option<(ConfigItem, String)>,
) {
    let ctx = screen.theme;

    let area = crate::tui::formatters::centered_rect(80, 60, f.area());
    f.render_widget(Clear, f.area());
    let block = Block::default()
        .title(Span::styled(
            "Config",
            ctx.apply(Style::default().fg(ctx.state_selected())),
        ))
        .borders(Borders::ALL)
        .border_style(ctx.apply(Style::default().fg(ctx.theme.semantic.border)));
    let inner_area = block.inner(area);
    f.render_widget(block, area);

    let settings_area = inner_area;
    let footer_y = area.y.saturating_add(area.height);
    let footer_area = if footer_y < f.area().y.saturating_add(f.area().height) {
        ratatui::layout::Rect::new(area.x, footer_y, area.width, 1)
    } else {
        // Fallback for very short terminals: keep commands visible at panel bottom.
        ratatui::layout::Rect::new(
            inner_area.x,
            inner_area.y + inner_area.height.saturating_sub(1),
            inner_area.width,
            1,
        )
    };
    let rows_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints(
            items
                .iter()
                .map(|_| Constraint::Length(1))
                .collect::<Vec<_>>(),
        )
        .split(settings_area);

    for (i, item) in items.iter().enumerate() {
        let (name_str, value_str) = match item {
            ConfigItem::ClientPort => ("Listen Port", settings.client_port.to_string()),
            ConfigItem::DefaultDownloadFolder => (
                "Default Download Folder",
                path_to_string(settings.default_download_folder.as_deref()),
            ),
            ConfigItem::WatchFolder => (
                "Torrent Watch Folder",
                path_to_string(settings.watch_folder.as_deref()),
            ),
            ConfigItem::GlobalDownloadLimit => (
                "Global DL Limit",
                format_limit_bps(settings.global_download_limit_bps),
            ),
            ConfigItem::GlobalUploadLimit => (
                "Global UL Limit",
                format_limit_bps(settings.global_upload_limit_bps),
            ),
        };

        let columns = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([Constraint::Percentage(60), Constraint::Percentage(40)])
            .split(rows_layout[i]);
        let is_highlighted = if let Some((edited_item, _)) = editing {
            *edited_item == *item
        } else {
            i == selected_index
        };
        let row_style = if is_highlighted {
            ctx.apply(Style::default().fg(ctx.state_warning()))
        } else {
            ctx.apply(Style::default().fg(ctx.theme.semantic.text))
        };
        let name_with_selector = if is_highlighted {
            format!("▶ {}", name_str)
        } else {
            format!("  {}", name_str)
        };

        let name_p = Paragraph::new(name_with_selector).style(row_style);
        f.render_widget(name_p, columns[0]);

        if let Some((_edited_item, buffer)) = editing {
            if is_highlighted {
                let edit_p =
                    Paragraph::new(buffer.as_str()).style(row_style.fg(ctx.state_warning()));
                f.set_cursor_position((columns[1].x + buffer.len() as u16, columns[1].y));
                f.render_widget(edit_p, columns[1]);
            } else {
                let value_p = Paragraph::new(value_str).style(row_style);
                f.render_widget(value_p, columns[1]);
            }
        } else {
            let value_p = Paragraph::new(value_str).style(row_style);
            f.render_widget(value_p, columns[1]);
        }
    }

    let shared_path_notice = crate::config::is_shared_config_mode()
        && items.get(selected_index) == Some(&ConfigItem::DefaultDownloadFolder);
    let help_text = if editing.is_some() {
        Line::from(vec![
            Span::styled(
                "[Enter]",
                ctx.apply(Style::default().fg(ctx.state_success())),
            ),
            Span::raw(" to confirm, "),
            Span::styled("[Esc]", ctx.apply(Style::default().fg(ctx.state_error()))),
            Span::raw(" to cancel."),
        ])
    } else if shared_path_notice {
        let settings_label = crate::config::shared_settings_path()
            .map(|path| path.to_string_lossy().to_string())
            .unwrap_or_else(|| "settings.toml".to_string());
        Line::from(vec![
            Span::raw("Shared mode: edit Default Download Folder in "),
            Span::styled(
                settings_label,
                ctx.apply(Style::default().fg(ctx.state_warning())),
            ),
            Span::raw(". Host-local fields still save here."),
        ])
    } else {
        Line::from(vec![
            Span::raw("Use "),
            Span::styled(
                "↑/↓/k/j",
                ctx.apply(Style::default().fg(ctx.state_warning())),
            ),
            Span::raw(" to navigate. "),
            Span::styled(
                "[Enter]",
                ctx.apply(Style::default().fg(ctx.state_warning())),
            ),
            Span::raw(" to edit. "),
            Span::styled("[r]", ctx.apply(Style::default().fg(ctx.state_warning()))),
            Span::raw("eset to default. "),
            Span::styled(
                "[Esc]|[Q]",
                ctx.apply(Style::default().fg(ctx.state_success())),
            ),
            Span::raw(" to Save & Exit, "),
        ])
    };

    let footer_paragraph = Paragraph::new(help_text)
        .alignment(Alignment::Center)
        .style(ctx.apply(Style::default().fg(ctx.theme.semantic.subtext1)));
    f.render_widget(footer_paragraph, footer_area);
}

pub fn handle_event(event: CrosstermEvent, ctx: ConfigHandleContext<'_>) -> bool {
    if let CrosstermEvent::Key(key) = event {
        if key.kind != KeyEventKind::Press {
            return false;
        }
        if let Some(action) = map_key_to_config_action(key.code, ctx.editing) {
            let reduced = reduce_config_action(
                action,
                ctx.settings_edit,
                ctx.selected_index,
                ctx.items,
                ctx.editing,
            );
            for effect in reduced.effects {
                match effect {
                    ConfigEffect::AppCommand(command) => {
                        let _ = ctx.app_command_tx.try_send(*command);
                    }
                    ConfigEffect::SetDownloadRate(new_rate) => {
                        let bucket = ctx.global_dl_bucket.clone();
                        tokio::spawn(async move {
                            bucket.set_rate(new_rate as f64);
                        });
                    }
                    ConfigEffect::SetUploadRate(new_rate) => {
                        let bucket = ctx.global_ul_bucket.clone();
                        tokio::spawn(async move {
                            bucket.set_rate(new_rate as f64);
                        });
                    }
                    ConfigEffect::ToNormal => {
                        *ctx.mode = AppMode::Normal;
                    }
                }
            }
            return reduced.consumed;
        }
    }

    false
}

#[cfg(test)]
mod tests {
    use super::*;

    fn config_items() -> Vec<ConfigItem> {
        vec![
            ConfigItem::ClientPort,
            ConfigItem::DefaultDownloadFolder,
            ConfigItem::WatchFolder,
            ConfigItem::GlobalDownloadLimit,
            ConfigItem::GlobalUploadLimit,
        ]
    }

    #[test]
    fn reducer_move_down_is_clamped() {
        let mut settings = Box::new(Settings::default());
        let mut idx = 0usize;
        let mut items = config_items();
        let mut editing = None;

        for _ in 0..10 {
            let _ = reduce_config_action(
                ConfigAction::MoveDown,
                &mut settings,
                &mut idx,
                items.as_mut_slice(),
                &mut editing,
            );
        }

        assert_eq!(idx, items.len() - 1);
    }

    #[test]
    fn reducer_edit_commit_updates_download_limit_and_emits_effect() {
        let mut settings = Box::new(Settings::default());
        let mut idx = 3usize;
        let mut items = config_items();
        let mut editing = Some((ConfigItem::GlobalDownloadLimit, "123".to_string()));

        let out = reduce_config_action(
            ConfigAction::EditCommit,
            &mut settings,
            &mut idx,
            items.as_mut_slice(),
            &mut editing,
        );

        assert_eq!(settings.global_download_limit_bps, 123);
        assert_eq!(editing, None);
        assert_eq!(out.effects.len(), 1);
        assert!(matches!(out.effects[0], ConfigEffect::SetDownloadRate(123)));
    }

    #[test]
    fn reducer_save_and_exit_emits_update_config_command() {
        let mut settings = Box::new(Settings::default());
        let mut idx = 0usize;
        let mut items = config_items();
        let mut editing = None;

        let out = reduce_config_action(
            ConfigAction::SaveAndExit,
            &mut settings,
            &mut idx,
            items.as_mut_slice(),
            &mut editing,
        );

        assert_eq!(out.effects.len(), 2);
        assert!(matches!(out.effects[0], ConfigEffect::AppCommand(_)));
        assert!(matches!(out.effects[1], ConfigEffect::ToNormal));
    }
}
