use super::SubMenu;
use crate::{
    components::{
        divider, format_indicator,
        icons::{StaticIcon, icon, icon_button},
        quick_setting_button, styled_button,
    },
    config::SettingsFormat,
    services::{
        ReadOnlyService, Service, ServiceEvent,
        network::{
            AccessPoint, ActiveConnectionInfo, KnownConnection, NetworkCommand, NetworkEvent,
            NetworkService, Vpn, dbus::ConnectivityState,
        },
    },
    t,
    theme::use_theme,
    utils::IndicatorState,
};
use iced::{
    Alignment, Element, Length, Padding, Subscription, SurfaceId, Task, Theme,
    widget::{Column, column, container, row, scrollable, text, toggler},
};
use log::{info, warn};

static WIFI_SIGNAL_ICONS: [StaticIcon; 6] = [
    StaticIcon::Wifi0,
    StaticIcon::Wifi1,
    StaticIcon::Wifi2,
    StaticIcon::Wifi3,
    StaticIcon::Wifi4,
    StaticIcon::Wifi5,
];

static WIFI_LOCK_SIGNAL_ICONS: [StaticIcon; 5] = [
    StaticIcon::WifiLock1,
    StaticIcon::WifiLock2,
    StaticIcon::WifiLock3,
    StaticIcon::WifiLock4,
    StaticIcon::WifiLock5,
];

fn get_connectivity_state(
    connectivity: ConnectivityState,
    indicator_state: IndicatorState,
) -> IndicatorState {
    match (connectivity, indicator_state) {
        (_, IndicatorState::Warning) => IndicatorState::Warning,
        (ConnectivityState::None, _) => IndicatorState::Danger,
        _ => IndicatorState::Normal,
    }
}

impl ActiveConnectionInfo {
    pub fn get_wifi_icon(signal: u8) -> StaticIcon {
        let clamped_signal = signal.min(100);
        WIFI_SIGNAL_ICONS[1 + f32::round(clamped_signal as f32 / 100. * 4.) as usize]
    }

    pub fn get_wifi_lock_icon(signal: u8) -> StaticIcon {
        let clamped_signal = signal.min(100);
        WIFI_LOCK_SIGNAL_ICONS[f32::round(clamped_signal as f32 / 100. * 4.) as usize]
    }

    pub fn get_icon(&self) -> StaticIcon {
        match self {
            Self::WiFi { strength, .. } => Self::get_wifi_icon(*strength),
            Self::Wired { .. } => StaticIcon::Ethernet,
            Self::Vpn { .. } => StaticIcon::Vpn,
        }
    }

    pub fn get_indicator_state(&self) -> IndicatorState {
        match self {
            Self::WiFi {
                strength: 0 | 1, ..
            } => IndicatorState::Warning,
            _ => IndicatorState::Normal,
        }
    }
}

#[derive(Debug, Clone)]
pub enum Message {
    Event(ServiceEvent<NetworkService>),
    ToggleWiFi,
    ScanNearByWiFi,
    WiFiMore(SurfaceId),
    VpnMore(SurfaceId),
    SelectAccessPoint(AccessPoint),
    RequestWiFiPassword(SurfaceId, String),
    ConfirmOpenNetwork(String),
    ToggleVpn(Vpn),
    ToggleAirplaneMode,
    OpenMore,
    ToggleWifiMenu,
    ToggleVPNMenu,
    WifiMenuOpened,
    PasswordDialogConfirmed(String, String),
    OpenNetworkDialogConfirmed(String),
    ConfigReloaded(NetworkSettingsConfig),
}

pub enum Action {
    None,
    RequestPasswordForSSID(String),
    RequestPassword(SurfaceId, String),
    ConfirmOpenNetwork(String),
    Command(Task<Message>),
    ToggleWifiMenu,
    ToggleVpnMenu,
    CloseSubMenu(Task<Message>),
    CloseMenu(SurfaceId),
}

#[derive(Debug, Clone)]
pub struct NetworkSettingsConfig {
    pub wifi_more_cmd: Option<String>,
    pub vpn_more_cmd: Option<String>,
    pub remove_airplane_btn: bool,
    pub indicator_format: SettingsFormat,
}

impl NetworkSettingsConfig {
    pub fn new(
        wifi_more_cmd: Option<String>,
        vpn_more_cmd: Option<String>,
        remove_airplane_btn: bool,
        indicator_format: SettingsFormat,
    ) -> Self {
        Self {
            wifi_more_cmd,
            vpn_more_cmd,
            remove_airplane_btn,
            indicator_format,
        }
    }
}

pub struct NetworkSettings {
    config: NetworkSettingsConfig,
    service: Option<NetworkService>,
}

impl NetworkSettings {
    pub fn new(config: NetworkSettingsConfig) -> Self {
        Self {
            config,
            service: None,
        }
    }

    pub fn is_airplane_mode(&self) -> Option<bool> {
        self.service.as_ref().map(|s| s.airplane_mode)
    }

    pub fn update(&mut self, message: Message) -> Action {
        match message {
            Message::Event(event) => match event {
                ServiceEvent::Init(service) => {
                    self.service = Some(service);
                    Action::None
                }
                ServiceEvent::Update(NetworkEvent::RequestPasswordForSSID(ssid)) => {
                    Action::RequestPasswordForSSID(ssid)
                }
                ServiceEvent::Update(data) => {
                    if let Some(service) = self.service.as_mut() {
                        service.update(data);
                    }
                    Action::None
                }
                _ => Action::None,
            },
            Message::ToggleAirplaneMode => match self.service.as_mut() {
                Some(service) => Action::CloseSubMenu(
                    service
                        .command(NetworkCommand::ToggleAirplaneMode)
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::ToggleWiFi => match self.service.as_mut() {
                Some(service) => Action::CloseSubMenu(
                    service
                        .command(NetworkCommand::ToggleWiFi)
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::SelectAccessPoint(ac) => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(NetworkCommand::SelectAccessPoint((ac, None)))
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::RequestWiFiPassword(id, ssid) => {
                info!("Requesting password for {ssid}");
                Action::RequestPassword(id, ssid)
            }
            Message::ConfirmOpenNetwork(ssid) => Action::ConfirmOpenNetwork(ssid),
            Message::ScanNearByWiFi => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(NetworkCommand::ScanNearByWiFi)
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::WiFiMore(id) => {
                if let Some(cmd) = &self.config.wifi_more_cmd {
                    crate::utils::launcher::execute_command(cmd);
                    Action::CloseMenu(id)
                } else {
                    Action::None
                }
            }
            Message::VpnMore(id) => {
                if let Some(cmd) = &self.config.vpn_more_cmd {
                    crate::utils::launcher::execute_command(cmd);
                    Action::CloseMenu(id)
                } else {
                    Action::None
                }
            }
            Message::ToggleVpn(vpn) => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(NetworkCommand::ToggleVpn(vpn))
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::OpenMore => {
                if let Some(cmd) = &self.config.wifi_more_cmd {
                    crate::utils::launcher::execute_command(cmd);
                }
                Action::None
            }
            Message::ToggleWifiMenu => Action::ToggleWifiMenu,
            Message::ToggleVPNMenu => Action::ToggleVpnMenu,
            Message::WifiMenuOpened => {
                if let Some(service) = self.service.as_mut() {
                    Action::Command(
                        service
                            .command(NetworkCommand::ScanNearByWiFi)
                            .map(Message::Event),
                    )
                } else {
                    Action::None
                }
            }
            Message::PasswordDialogConfirmed(ssid, password) => match self.service.as_mut() {
                Some(service) => {
                    let ap = service
                        .wireless_access_points
                        .iter()
                        .find(|ap| ap.ssid == ssid)
                        .cloned();
                    if let Some(ap) = ap {
                        let password = (!password.is_empty()).then_some(password);
                        Action::Command(
                            service
                                .command(NetworkCommand::SelectAccessPoint((ap, password)))
                                .map(Message::Event),
                        )
                    } else {
                        warn!(
                            "Unable to confirm password dialog: access point '{ssid}' no longer available"
                        );
                        Action::None
                    }
                }
                _ => Action::None,
            },
            Message::OpenNetworkDialogConfirmed(ssid) => match self.service.as_mut() {
                Some(service) => {
                    let ap = service
                        .wireless_access_points
                        .iter()
                        .find(|ap| ap.ssid == ssid)
                        .cloned();
                    if let Some(ap) = ap {
                        Action::Command(
                            service
                                .command(NetworkCommand::SelectAccessPoint((ap, None)))
                                .map(Message::Event),
                        )
                    } else {
                        warn!(
                            "Unable to confirm open network dialog: access point '{ssid}' no longer available"
                        );
                        Action::None
                    }
                }
                _ => Action::None,
            },
            Message::ConfigReloaded(config) => {
                self.config = config;
                Action::None
            }
        }
    }

    pub fn connection_indicator<'a>(&'a self) -> Option<Element<'a, Message>> {
        self.service.as_ref().and_then(|service| {
            if service.airplane_mode || !service.wifi_present {
                None
            } else {
                let active = service.active_connections.iter().find(|c| {
                    matches!(c, ActiveConnectionInfo::WiFi { .. })
                        || matches!(c, ActiveConnectionInfo::Wired { .. })
                });

                Some(if let Some(a) = active {
                    let icon_type = a.get_icon();
                    let state =
                        get_connectivity_state(service.connectivity, a.get_indicator_state());
                    let strength = match a {
                        ActiveConnectionInfo::WiFi { strength, .. } => Some(*strength),
                        _ => None,
                    };
                    let strength_text = strength.map_or("100%".to_string(), |s| format!("{}%", s));

                    format_indicator(
                        self.config.indicator_format,
                        icon_type,
                        text(strength_text).into(),
                        state,
                    )
                    .on_right_press(Message::OpenMore)
                    .into()
                } else {
                    format_indicator(
                        self.config.indicator_format,
                        StaticIcon::Wifi0,
                        text("0%").into(),
                        IndicatorState::Normal,
                    )
                    .on_right_press(Message::OpenMore)
                    .into()
                })
            }
        })
    }

    pub fn vpn_indicator<'a>(&'a self) -> Option<Element<'a, Message>> {
        self.service.as_ref().and_then(|service| {
            service
                .active_connections
                .iter()
                .find(|c| matches!(c, ActiveConnectionInfo::Vpn { .. }))
                .map(|a| {
                    let icon_type = a.get_icon();

                    container(icon(icon_type))
                        .style(|theme: &Theme| container::Style {
                            text_color: Some(theme.palette().warning),
                            ..Default::default()
                        })
                        .into()
                })
        })
    }

    pub fn wifi_quick_setting_button<'a>(
        &'a self,
        id: SurfaceId,
        sub_menu: Option<SubMenu>,
    ) -> Option<(Element<'a, Message>, Option<Element<'a, Message>>)> {
        self.service.as_ref().and_then(|service| {
            if service.wifi_present {
                let active_connection = service.active_connections.iter().find_map(|c| match c {
                    ActiveConnectionInfo::WiFi { name, strength, .. } => {
                        Some((name, strength, c.get_icon()))
                    }
                    _ => None,
                });

                Some((
                    quick_setting_button(
                        active_connection.map_or_else(|| StaticIcon::Wifi0, |(_, _, icon)| icon),
                        t!("settings-network-wifi"),
                        active_connection.map(|(name, _, _)| name.to_string()),
                        service.wifi_enabled,
                        Message::ToggleWiFi,
                        Some(Message::OpenMore),
                        service.wifi_enabled.then_some((
                            SubMenu::Wifi,
                            sub_menu,
                            Message::ToggleWifiMenu,
                        )),
                    ),
                    sub_menu
                        .filter(|menu_type| *menu_type == SubMenu::Wifi)
                        .map(|_| {
                            Self::wifi_menu(
                                service,
                                id,
                                active_connection
                                    .map(|(name, strength, _)| (name.as_str(), *strength)),
                                self.config.wifi_more_cmd.is_some(),
                            )
                        }),
                ))
            } else {
                None
            }
        })
    }

    pub fn vpn_quick_setting_button<'a>(
        &'a self,
        id: SurfaceId,
        sub_menu: Option<SubMenu>,
    ) -> Option<(Element<'a, Message>, Option<Element<'a, Message>>)> {
        self.service.as_ref().and_then(|service| {
            service
                .known_connections
                .iter()
                .any(|c| matches!(c, KnownConnection::Vpn { .. }))
                .then(|| {
                    // Create HashMap for O(1) lookup of known VPNs
                    let known_vpn_map: std::collections::HashMap<&str, &Vpn> = service
                        .known_connections
                        .iter()
                        .filter_map(|c| match c {
                            KnownConnection::Vpn(vpn) => Some((vpn.name.as_str(), vpn)),
                            _ => None,
                        })
                        .collect();

                    // Find active VPNs using O(1) lookup
                    let actives: Vec<&Vpn> = service
                        .active_connections
                        .iter()
                        .filter_map(|c| match c {
                            ActiveConnectionInfo::Vpn { name, .. } => {
                                known_vpn_map.get(name.as_str()).copied()
                            }
                            _ => None,
                        })
                        .collect();

                    let subtitle = if actives.len() > 1 {
                        Some(t!("settings-network-vpns-connected", count = actives.len()))
                    } else {
                        actives.first().map(|c| c.name.to_string())
                    };

                    (
                        quick_setting_button(
                            StaticIcon::Vpn,
                            t!("settings-network-vpn"),
                            subtitle,
                            !actives.is_empty(),
                            if !actives.is_empty()
                                && let Some(first) = actives.first()
                            {
                                Message::ToggleVpn((*first).clone())
                            } else {
                                Message::ToggleVPNMenu
                            },
                            Some(Message::OpenMore),
                            if !actives.is_empty() {
                                Some((SubMenu::Vpn, sub_menu, Message::ToggleVPNMenu))
                            } else {
                                None
                            },
                        ),
                        sub_menu
                            .filter(|menu_type| *menu_type == SubMenu::Vpn)
                            .map(|_| {
                                Self::vpn_menu(service, id, self.config.vpn_more_cmd.is_some())
                            }),
                    )
                })
        })
    }

    pub fn airplane_mode_quick_setting_button<'a>(
        &'a self,
    ) -> Option<(Element<'a, Message>, Option<Element<'a, Message>>)> {
        if self.config.remove_airplane_btn {
            None
        } else {
            self.service.as_ref().map(|service| {
                (
                    quick_setting_button(
                        Self::airplane_mode_icon(service.airplane_mode),
                        t!("settings-network-airplane-mode"),
                        None,
                        service.airplane_mode,
                        Message::ToggleAirplaneMode,
                        Some(Message::OpenMore),
                        None,
                    ),
                    None,
                )
            })
        }
    }

    fn wifi_menu<'a>(
        service: &'a NetworkService,
        id: SurfaceId,
        active_connection: Option<(&str, u8)>,
        show_more_button: bool,
    ) -> Element<'a, Message> {
        let (space, font_size) = use_theme(|t| (t.space, t.font_size));
        let main = column!(
            row!(
                text(t!("settings-network-nearby-wifi")).width(Length::Fill),
                text(if service.scanning_nearby_wifi {
                    t!("settings-scanning")
                } else {
                    String::new()
                })
                .size(font_size.sm),
                icon_button(StaticIcon::Refresh).on_press(Message::ScanNearByWiFi)
            )
            .spacing(space.xs)
            .width(Length::Fill)
            .align_y(Alignment::Center),
            divider(),
            container(scrollable(
                Column::with_children({
                    let (active_networks, inactive_networks): (Vec<_>, Vec<_>) = service
                        .wireless_access_points
                        .iter()
                        .partition(|ac| active_connection.is_some_and(|(ssid, _)| ssid == ac.ssid));

                    active_networks
                        .into_iter()
                        .map(|ac| (ac, true))
                        .chain(inactive_networks.into_iter().map(|ac| (ac, false)))
                        .map(|(ac, is_active)| {
                            let is_known = service.known_connections.iter().any(|c| {
                                matches!(
                                    c,
                                    KnownConnection::AccessPoint(AccessPoint { ssid, .. }) if ssid == &ac.ssid
                                )
                            });

                            styled_button(
                                Element::from(
                                    container(
                                        row!(
                                            icon(if ac.public {
                                                ActiveConnectionInfo::get_wifi_icon(ac.strength)
                                            } else {
                                                ActiveConnectionInfo::get_wifi_lock_icon(ac.strength)
                                            })
                                            .width(Length::Shrink),
                                            text(ac.ssid.as_str()).width(Length::Fill),
                                        )
                                        .align_y(Alignment::Center)
                                        .spacing(8),
                                    )
                                    .style(move |theme: &Theme| {
                                        container::Style {
                                            text_color: if is_active {
                                                Some(theme.palette().success)
                                            } else {
                                                None
                                            },
                                            ..Default::default()
                                        }
                                    }),
                                ),
                            )
                            .on_press_maybe(if !is_active {
                                Some(if ac.public {
                                    Message::ConfirmOpenNetwork(ac.ssid.to_string())
                                } else if is_known {
                                    Message::SelectAccessPoint(ac.clone())
                                } else {
                                    Message::RequestWiFiPassword(id, ac.ssid.to_string())
                                })
                            } else {
                                None
                            })
                            .width(Length::Fill)
                            .into()
                        })
                        .collect::<Vec<Element<'a, Message>>>()
                })
                .spacing(space.xxs)
            ).spacing(space.xs))
            .max_height(200),
        )
        .spacing(space.xs);

        if show_more_button {
            column!(
                main,
                divider(),
                styled_button(t!("settings-more"))
                    .on_press(Message::WiFiMore(id))
                    .width(Length::Fill)
            )
            .spacing(space.sm)
            .into()
        } else {
            main.into()
        }
    }

    fn vpn_menu<'a>(
        service: &'a NetworkService,
        id: SurfaceId,
        show_more_button: bool,
    ) -> Element<'a, Message> {
        let space = use_theme(|t| t.space);
        // Create HashSet of active VPN names for O(1) lookup
        let active_vpn_names: std::collections::HashSet<&str> = service
            .active_connections
            .iter()
            .filter_map(|c| match c {
                ActiveConnectionInfo::Vpn { name, .. } => Some(name.as_str()),
                _ => None,
            })
            .collect();

        // Collect and sort the VPNs
        let mut vpns: Vec<_> = service
            .known_connections
            .iter()
            .filter_map(|c| match c {
                KnownConnection::Vpn(vpn) => Some(vpn),
                _ => None,
            })
            .collect();

        vpns.sort_by_key(|a| a.name.clone());

        let vpn_list = Column::with_children(
            vpns.into_iter()
                .map(|vpn| {
                    // O(1) lookup instead of O(n) .any() call
                    let is_active = active_vpn_names.contains(vpn.name.as_str());

                    row!(
                        text(vpn.name.to_string()).width(Length::Fill),
                        toggler(is_active)
                            .on_toggle(|_| { Message::ToggleVpn(vpn.clone()) })
                            .width(Length::Shrink),
                    )
                    .into()
                })
                .collect::<Vec<Element<'a, Message>>>(),
        )
        .spacing(space.xs)
        .padding(Padding::default().right(space.md).left(space.xs));

        let main = container(scrollable(vpn_list))
            .height(Length::Shrink)
            .max_height(300);

        if show_more_button {
            column!(
                main,
                divider(),
                styled_button(t!("settings-more"))
                    .on_press(Message::VpnMore(id))
                    .width(Length::Fill)
            )
            .spacing(space.sm)
            .into()
        } else {
            main.into()
        }
    }

    pub fn subscription(&self) -> Subscription<Message> {
        NetworkService::subscribe().map(Message::Event)
    }

    pub fn airplane_mode_icon(active: bool) -> StaticIcon {
        if active {
            StaticIcon::AirplaneOn
        } else {
            StaticIcon::AirplaneOff
        }
    }

    pub fn connected_wifi_label(&self) -> Option<String> {
        self.service.as_ref().and_then(|service| {
            service.active_connections.iter().find_map(|c| match c {
                ActiveConnectionInfo::WiFi { name, .. } => Some(name.clone()),
                _ => None,
            })
        })
    }

    pub fn vpn_tooltip_label(&self) -> Option<String> {
        self.service.as_ref().and_then(|service| {
            let active_vpns: Vec<_> = service
                .active_connections
                .iter()
                .filter_map(|c| match c {
                    ActiveConnectionInfo::Vpn { name, .. } => Some(name.clone()),
                    _ => None,
                })
                .collect();

            if active_vpns.is_empty() {
                None
            } else if active_vpns.len() == 1 {
                Some(active_vpns[0].clone())
            } else {
                Some(format!("{} VPNs connected", active_vpns.len()))
            }
        })
    }
}
