//! Settings view — security tier + per-field toggles. Pure
//! rendering; the App layer marshals the [`SecurityConfig`] across
//! the worker boundary.

use eframe::egui;

use crate::worker::ReqId;
use envseal::security_config::{InputMethod, SecurityConfig, SecurityTier};

#[derive(Default)]
pub struct SettingsState {
    /// Last loaded config — `None` until first refresh completes.
    pub config: Option<SecurityConfig>,
    pending_load: Option<ReqId>,
    pending_save: Option<ReqId>,
    pub error: Option<String>,
}

impl SettingsState {
    pub fn start_refresh(&mut self, id: ReqId) {
        self.pending_load = Some(id);
        self.error = None;
    }

    pub fn apply_loaded(&mut self, id: ReqId, config: SecurityConfig) {
        if self.pending_load != Some(id) {
            return;
        }
        self.pending_load = None;
        self.config = Some(config);
        self.error = None;
    }

    pub fn apply_error(&mut self, id: ReqId, err: String) {
        if self.pending_load == Some(id) {
            self.pending_load = None;
            self.error = Some(err);
        } else if self.pending_save == Some(id) {
            self.pending_save = None;
            self.error = Some(err);
        }
    }

    pub fn start_save(&mut self, id: ReqId) {
        self.pending_save = Some(id);
    }

    pub fn finish_save(&mut self) {
        self.pending_save = None;
    }

    fn is_loading(&self) -> bool {
        self.pending_load.is_some()
    }

    fn is_saving(&self) -> bool {
        self.pending_save.is_some()
    }
}

#[derive(Default)]
pub struct SettingsAction {
    pub save: Option<SecurityConfig>,
    pub refresh: bool,
}

pub fn render(ui: &mut egui::Ui, state: &mut SettingsState) -> SettingsAction {
    let mut action = SettingsAction::default();

    ui.add_space(10.0);
    ui.horizontal(|ui| {
        ui.heading("Security Settings");
        if state.is_loading() {
            ui.add_space(8.0);
            ui.spinner();
        }
        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
            if ui.button("Refresh").clicked() {
                action.refresh = true;
            }
        });
    });
    ui.add_space(12.0);

    if let Some(err) = &state.error {
        ui.colored_label(egui::Color32::from_rgb(0xf5, 0x9e, 0x0b), err);
        ui.add_space(8.0);
    }

    if state.config.is_none() {
        ui.add_space(20.0);
        ui.vertical_centered(|ui| {
            ui.label(egui::RichText::new("Settings will load after the vault is unlocked.").weak());
        });
        return action;
    }
    let saving = state.is_saving();
    let Some(config) = state.config.as_mut() else {
        return action;
    };

    egui::ScrollArea::vertical().show(ui, |ui| {
        section(ui, "Security tier", |ui| {
            ui.label(
                egui::RichText::new("Quick presets that set every other field on this page.")
                    .size(12.0)
                    .weak(),
            );
            ui.add_space(8.0);
            ui.horizontal(|ui| {
                tier_button(
                    ui,
                    &mut config.tier,
                    SecurityTier::Standard,
                    "Standard",
                    "Zero friction",
                );
                tier_button(
                    ui,
                    &mut config.tier,
                    SecurityTier::Hardened,
                    "Hardened",
                    "2s delay, virtual keyboard",
                );
                tier_button(
                    ui,
                    &mut config.tier,
                    SecurityTier::Lockdown,
                    "Lockdown",
                    "Challenge + 5s + hard block",
                );
            });
        });

        section(ui, "Authentication", |ui| {
            ui.checkbox(
                &mut config.challenge_required,
                "Require challenge code before approval",
            );
            ui.add_space(4.0);
            ui.horizontal(|ui| {
                ui.label("Approval delay:");
                ui.add(egui::Slider::new(&mut config.approval_delay_secs, 0..=30).suffix("s"));
            });
            ui.add_space(4.0);
            ui.horizontal(|ui| {
                ui.label("Auto-lock vault after:");
                ui.add(egui::Slider::new(&mut config.auto_lock_secs, 0..=3600).suffix("s"));
            });
            ui.add_space(4.0);
            ui.horizontal(|ui| {
                ui.label("Passphrase input:");
                input_method_selector(ui, &mut config.input_method);
            });
            ui.add_space(4.0);
            ui.horizontal(|ui| {
                ui.label("Custom UI command (absolute path):");
                let mut cmd = config.custom_ui_cmd.clone().unwrap_or_default();
                if ui.text_edit_singleline(&mut cmd).changed() {
                    config.custom_ui_cmd = if cmd.trim().is_empty() {
                        None
                    } else {
                        Some(cmd.trim().to_string())
                    };
                }
            });
        });

        section(ui, "Rate limiting", |ui| {
            ui.label(
                egui::RichText::new(
                    "Cap how often agents can pop approval dialogs. Prevents \
                     approval-fatigue attacks where 100 popups appear hoping you reflex-click Allow.",
                )
                .size(11.5)
                .weak(),
            );
            ui.add_space(6.0);
            ui.horizontal(|ui| {
                ui.label("Min cooldown between popups:");
                ui.add(
                    egui::Slider::new(&mut config.approval_cooldown_secs, 0..=60).suffix("s"),
                );
            });
            ui.add_space(4.0);
            ui.horizontal(|ui| {
                ui.label("Max popups per minute:");
                ui.add(egui::Slider::new(
                    &mut config.max_approvals_per_minute,
                    0..=100,
                ));
            });
        });

        section(ui, "Challenge & Delay", |ui| {
            ui.label(
                egui::RichText::new(
                    "Slow down the approval process and require manual transcription \
                     to defeat script-driven 'SendKeys' automation.",
                )
                .size(11.5)
                .weak(),
            );
            ui.add_space(6.0);
            ui.checkbox(
                &mut config.challenge_required,
                "Require 4-digit challenge code",
            );
            ui.add_space(4.0);
            ui.horizontal(|ui| {
                ui.label("Approval delay (seconds before popup appears):");
                ui.add(egui::Slider::new(&mut config.approval_delay_secs, 0..=30).suffix("s"));
            });
        });

        section(ui, "Vault Auto-Lock", |ui| {
            ui.label(
                egui::RichText::new(
                    "Seconds of GUI inactivity after which the unlocked vault drops its \
                     keys, forcing the next operation to re-prompt for the passphrase.",
                )
                .size(11.5)
                .weak(),
            );
            ui.add_space(6.0);
            ui.horizontal(|ui| {
                ui.label("Auto-lock after:");
                ui.add(egui::Slider::new(&mut config.auto_lock_secs, 0..=3600).suffix("s"));
            });
        });

        section(ui, "Rule lifetime — how long does Allow Always last?", |ui| {
            ui.label(
                egui::RichText::new(
                    "Bound the blast radius of an Allow Always click. After the duration, \
                     the rule expires and the next access re-prompts. CTF mode forces a \
                     low cap regardless of this setting.",
                )
                .size(11.5)
                .weak(),
            );
            ui.add_space(6.0);
            ui.horizontal(|ui| {
                ui.label("Default Allow Always duration:");
                rule_expiry_selector(ui, &mut config.default_rule_expiry_secs);
            });
            ui.add_space(4.0);
            ui.horizontal(|ui| {
                ui.label("Maximum allowed duration:");
                rule_expiry_selector(ui, &mut config.max_rule_expiry_secs);
            });
        });

        section(ui, "Relay (out-of-band approval)", |ui| {
            ui.label(
                egui::RichText::new(
                    "Send approval prompts to a paired phone instead of (or in addition to) \
                     the local GUI. With `relay required` ON, envseal fails closed if the \
                     relay can't be reached — no fallback to local popup.",
                )
                .size(11.5)
                .weak(),
            );
            ui.add_space(6.0);
            ui.checkbox(
                &mut config.relay_required,
                "Relay required (fail closed if relay unreachable)",
            );
            ui.add_space(4.0);
            relay_endpoint_input(ui, &mut config.relay_endpoint);
            ui.add_space(4.0);
            relay_text_input(
                ui,
                "Device ID:",
                &mut config.relay_device_id,
                "set via `envseal security relay-pair`",
            );
            if config.relay_pairing_key.is_some() {
                ui.add_space(4.0);
                ui.colored_label(
                    egui::Color32::from_rgb(0x22, 0xc5, 0x5e),
                    "✓ Relay pairing key present",
                );
            }
        });

        section(ui, "Environment", |ui| {
            ui.checkbox(
                &mut config.x11_auto_upgrade,
                "Auto-upgrade to virtual keyboard on X11",
            );
            ui.add_space(4.0);
            ui.checkbox(
                &mut config.warn_untrusted_binary,
                "Warn for binaries in /tmp, ~/Downloads, etc",
            );
            ui.add_space(6.0);
            ui.label(
                egui::RichText::new(
                    "Per-detector severity / per-tier action overrides are managed via \
                     the Signal taxonomy — see the read-only summary below or use \
                     `envseal security override-signal` / `override-tier` from the CLI.",
                )
                .size(11.0)
                .weak(),
            );
        });

        section(ui, "Signal & tier overrides (read-only)", |ui| {
            if config.signal_overrides.is_empty() && config.tier_overrides.is_empty() {
                ui.label(
                    egui::RichText::new("No overrides set — using default policy table.")
                        .size(11.5)
                        .weak(),
                );
            } else {
                if !config.signal_overrides.is_empty() {
                    ui.label(
                        egui::RichText::new(format!(
                            "{} signal-id override(s):",
                            config.signal_overrides.len()
                        ))
                        .strong(),
                    );
                    for (id, action) in &config.signal_overrides {
                        ui.label(
                            egui::RichText::new(format!("  {id} → {action}"))
                                .monospace()
                                .size(11.5),
                        );
                    }
                }
                if !config.tier_overrides.is_empty() {
                    ui.add_space(4.0);
                    ui.label(
                        egui::RichText::new(format!(
                            "{} (severity, tier) override(s):",
                            config.tier_overrides.len()
                        ))
                        .strong(),
                    );
                    for (key, action) in &config.tier_overrides {
                        ui.label(
                            egui::RichText::new(format!("  {key} → {action}"))
                                .monospace()
                                .size(11.5),
                        );
                    }
                }
                ui.add_space(6.0);
                ui.label(
                    egui::RichText::new(
                        "Edit via the CLI: envseal security override-signal <id> <action>",
                    )
                    .size(10.5)
                    .weak(),
                );
            }
        });

        section(ui, "Audit & 2FA", |ui| {
            ui.checkbox(&mut config.audit_logging, "Log all security events");
            ui.add_space(4.0);
            if config.totp_required {
                ui.colored_label(egui::Color32::from_rgb(0x22, 0xc5, 0x5e), "TOTP enabled");
            } else {
                ui.label("TOTP disabled");
                ui.label(
                    egui::RichText::new("Run: envseal security totp-setup")
                        .size(11.0)
                        .weak(),
                );
            }
        });

        ui.add_space(16.0);
        ui.horizontal(|ui| {
            let label = if saving { "Saving…" } else { "Save changes" };
            let btn = ui.add_enabled(
                !saving,
                egui::Button::new(label)
                    .min_size(egui::vec2(160.0, 30.0))
                    .corner_radius(6.0),
            );
            if btn.clicked() && !saving {
                action.save = Some(config.clone());
            }
        });
    });

    action
}

fn section(ui: &mut egui::Ui, title: &str, content: impl FnOnce(&mut egui::Ui)) {
    ui.add_space(4.0);
    egui::Frame::new()
        .fill(ui.visuals().faint_bg_color)
        .corner_radius(8.0)
        .inner_margin(14.0)
        .show(ui, |ui| {
            ui.label(egui::RichText::new(title).strong().size(14.0));
            ui.add_space(6.0);
            content(ui);
        });
    ui.add_space(6.0);
}

/// Dropdown for an `Option<u64>` rule-expiry value with a few
/// preset durations + "Forever" + "Custom (preserved)". When the
/// stored value matches a preset exactly we show the preset label;
/// any other value renders as "{N}s (custom)" so editing the file
/// directly doesn't get clobbered by switching the dropdown.
fn rule_expiry_selector(ui: &mut egui::Ui, current: &mut Option<u64>) {
    const ONE_HOUR: u64 = 3600;
    const ONE_DAY: u64 = 86_400;
    const SEVEN_DAYS: u64 = 7 * ONE_DAY;
    const THIRTY_DAYS: u64 = 30 * ONE_DAY;
    const NINETY_DAYS: u64 = 90 * ONE_DAY;

    let label = match *current {
        None => "Forever".to_string(),
        Some(s) if s == ONE_HOUR => "1 hour".to_string(),
        Some(s) if s == ONE_DAY => "24 hours".to_string(),
        Some(s) if s == SEVEN_DAYS => "7 days".to_string(),
        Some(s) if s == THIRTY_DAYS => "30 days".to_string(),
        Some(s) if s == NINETY_DAYS => "90 days".to_string(),
        Some(s) => format!("{s}s (custom)"),
    };

    egui::ComboBox::from_id_salt(format!("expiry_combo_{:p}", current as *const _))
        .selected_text(label)
        .show_ui(ui, |ui| {
            ui.selectable_value(current, None, "Forever");
            ui.selectable_value(current, Some(ONE_HOUR), "1 hour");
            ui.selectable_value(current, Some(ONE_DAY), "24 hours");
            ui.selectable_value(current, Some(SEVEN_DAYS), "7 days");
            ui.selectable_value(current, Some(THIRTY_DAYS), "30 days");
            ui.selectable_value(current, Some(NINETY_DAYS), "90 days");
        });
}

fn input_method_selector(ui: &mut egui::Ui, current: &mut InputMethod) {
    egui::ComboBox::from_id_salt("input_method_combo")
        .selected_text(match current {
            InputMethod::Keyboard => "Keyboard",
            InputMethod::VirtualKeyboard => "Virtual keyboard",
        })
        .show_ui(ui, |ui| {
            ui.selectable_value(current, InputMethod::Keyboard, "Keyboard");
            ui.selectable_value(
                current,
                InputMethod::VirtualKeyboard,
                "Virtual keyboard (mouse-click)",
            );
        });
}

fn relay_endpoint_input(ui: &mut egui::Ui, endpoint: &mut Option<String>) {
    let mut buf = endpoint.clone().unwrap_or_default();
    let placeholder = "https://relay.example.com (empty = unset)";
    ui.horizontal(|ui| {
        ui.label("Endpoint:");
        let resp = ui.add(
            egui::TextEdit::singleline(&mut buf)
                .hint_text(placeholder)
                .desired_width(360.0),
        );
        if resp.changed() {
            *endpoint = if buf.trim().is_empty() {
                None
            } else {
                Some(buf.clone())
            };
        }
    });
}

fn relay_text_input(ui: &mut egui::Ui, label: &str, field: &mut Option<String>, placeholder: &str) {
    let mut buf = field.clone().unwrap_or_default();
    ui.horizontal(|ui| {
        ui.label(label);
        let resp = ui.add(
            egui::TextEdit::singleline(&mut buf)
                .hint_text(placeholder)
                .desired_width(360.0),
        );
        if resp.changed() {
            *field = if buf.trim().is_empty() {
                None
            } else {
                Some(buf.clone())
            };
        }
    });
}

fn tier_button(
    ui: &mut egui::Ui,
    current: &mut SecurityTier,
    value: SecurityTier,
    name: &str,
    desc: &str,
) {
    let selected = *current == value;
    let fill = if selected {
        egui::Color32::from_rgb(0x3b, 0x82, 0xf6)
    } else {
        ui.visuals().widgets.inactive.bg_fill
    };
    let text_color = if selected {
        egui::Color32::WHITE
    } else {
        ui.visuals().text_color()
    };
    let btn = ui.add(
        egui::Button::new(
            egui::RichText::new(format!("{name}\n{desc}"))
                .size(11.5)
                .color(text_color),
        )
        .fill(fill)
        .min_size(egui::vec2(150.0, 46.0))
        .corner_radius(8.0),
    );
    if btn.clicked() {
        *current = value;
    }
}
