use super::SubMenu;
use crate::{
    components::{
        ButtonSize, divider, format_indicator,
        icons::{StaticIcon, icon, icon_button},
        quick_setting_button, styled_button,
    },
    config::SettingsFormat,
    services::{
        ReadOnlyService, Service, ServiceEvent,
        bluetooth::{BluetoothCommand, BluetoothDevice, BluetoothService, BluetoothState},
    },
    t,
    theme::{AshellTheme, use_theme},
    utils::IndicatorState,
};
use iced::{
    Element, Length, Padding, Subscription, SurfaceId, Task, Theme,
    alignment::{Horizontal, Vertical},
    widget::{Column, MouseArea, Row, column, container, row, scrollable, text},
};
use itertools::Itertools;
use zbus::zvariant::OwnedObjectPath;

#[derive(Debug, Clone)]
pub enum Message {
    Event(ServiceEvent<BluetoothService>),
    Toggle,
    ToggleSubMenu,
    StartDiscovery,
    StopDiscovery,
    PairDevice(OwnedObjectPath),
    ConnectDevice(OwnedObjectPath),
    DisconnectDevice(OwnedObjectPath),
    RemoveDevice(OwnedObjectPath),
    OpenMore,
    More(SurfaceId),
    ConfigReloaded(BluetoothSettingsConfig),
}

pub enum Action {
    None,
    ToggleBluetoothMenu,
    CloseMenu(SurfaceId),
    CloseSubMenu(Task<Message>),
    Command(Task<Message>),
}

#[derive(Debug, Clone)]
pub struct BluetoothSettingsConfig {
    pub more_cmd: Option<String>,
    pub indicator_format: SettingsFormat,
}

impl BluetoothSettingsConfig {
    pub fn new(more_cmd: Option<String>, indicator_format: SettingsFormat) -> Self {
        Self {
            more_cmd,
            indicator_format,
        }
    }
}

pub struct BluetoothSettings {
    config: BluetoothSettingsConfig,
    service: Option<BluetoothService>,
}

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

    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(data) => {
                    if let Some(service) = self.service.as_mut() {
                        service.update(data);
                    }
                    Action::None
                }
                _ => Action::None,
            },
            Message::Toggle => match self.service.as_mut() {
                Some(service) => Action::CloseSubMenu(
                    service
                        .command(BluetoothCommand::Toggle)
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::ToggleSubMenu => Action::ToggleBluetoothMenu,
            Message::StartDiscovery => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(BluetoothCommand::StartDiscovery)
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::StopDiscovery => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(BluetoothCommand::StopDiscovery)
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::PairDevice(device_path) => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(BluetoothCommand::PairDevice(device_path))
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::ConnectDevice(device_path) => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(BluetoothCommand::ConnectDevice(device_path))
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::DisconnectDevice(device_path) => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(BluetoothCommand::DisconnectDevice(device_path))
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::RemoveDevice(device_path) => match self.service.as_mut() {
                Some(service) => Action::Command(
                    service
                        .command(BluetoothCommand::RemoveDevice(device_path))
                        .map(Message::Event),
                ),
                _ => Action::None,
            },
            Message::OpenMore => {
                if let Some(cmd) = &self.config.more_cmd {
                    crate::utils::launcher::execute_command(cmd);
                }
                Action::None
            }
            Message::More(id) => {
                if let Some(cmd) = &self.config.more_cmd {
                    crate::utils::launcher::execute_command(cmd);

                    Action::CloseMenu(id)
                } else {
                    Action::None
                }
            }
            Message::ConfigReloaded(config) => {
                self.config = config;
                Action::None
            }
        }
    }

    pub fn quick_setting_button<'a>(
        &'a self,
        id: SurfaceId,
        sub_menu: Option<SubMenu>,
    ) -> Option<(Element<'a, Message>, Option<Element<'a, Message>>)> {
        if let Some(service) = &self.service
            && service.state != BluetoothState::Unavailable
        {
            // Get connected devices names
            let connected_devices: Vec<_> = service
                .devices
                .iter()
                .filter(|d| d.connected)
                .map(|d| d.name.clone())
                .collect();

            let device_name = match connected_devices.len() {
                0 => None,
                1 => Some(connected_devices[0].clone()),
                n => Some(t!("settings-bluetooth-connected-count", count = n)),
            };

            Some((
                quick_setting_button(
                    if service.state == BluetoothState::Active {
                        StaticIcon::Bluetooth
                    } else {
                        StaticIcon::BluetoothOff
                    },
                    t!("settings-bluetooth"),
                    device_name,
                    service.state == BluetoothState::Active,
                    Message::Toggle,
                    Some(Message::OpenMore),
                    (service.state == BluetoothState::Active).then_some((
                        SubMenu::Bluetooth,
                        sub_menu,
                        Message::ToggleSubMenu,
                    )),
                ),
                sub_menu
                    .filter(|menu_type| *menu_type == SubMenu::Bluetooth)
                    .and_then(|_| self.bluetooth_menu(id)),
            ))
        } else {
            None
        }
    }

    fn bluetooth_menu<'a>(&'a self, id: SurfaceId) -> Option<Element<'a, Message>> {
        use_theme(|theme| self.bluetooth_menu_with_theme(id, theme))
    }

    fn bluetooth_menu_with_theme<'a>(
        &'a self,
        id: SurfaceId,
        theme: &AshellTheme,
    ) -> Option<Element<'a, Message>> {
        self.service.as_ref().map(|service| {
            let connected_devices = service
                .devices
                .iter()
                .filter(|d| d.connected)
                .sorted_by_key(|d| &d.name);
            let paired_devices = service
                .devices
                .iter()
                .filter(|d| d.paired && !d.connected)
                .sorted_by_key(|d| &d.name);

            let mut known_devices = connected_devices.chain(paired_devices).peekable();
            let mut available_devices = service
                .devices
                .iter()
                .filter(|d| !d.paired && !d.connected)
                .peekable();

            let some_known = known_devices.peek().is_some();
            let some_available = available_devices.peek().is_some();

            Column::with_capacity(6)
                .push(
                    row![
                        text(t!("settings-bluetooth-devices")).width(Length::Fill),
                        text(if service.discovering {
                            t!("settings-scanning")
                        } else {
                            String::new()
                        })
                        .size(theme.font_size.xs),
                        icon_button(if service.discovering {
                            StaticIcon::Close
                        } else {
                            StaticIcon::Refresh
                        })
                        .on_press(if service.discovering {
                            Message::StopDiscovery
                        } else {
                            Message::StartDiscovery
                        })
                    ]
                    .align_y(Vertical::Center)
                    .spacing(theme.space.xs)
                    .width(Length::Fill),
                )
                .push(if some_known {
                    let known_device_entry = |d: &BluetoothDevice| {
                        styled_button(Element::from(
                            Row::with_capacity(3)
                                .push(
                                    text(d.name.clone())
                                        .color_maybe(if d.connected {
                                            Some(theme.iced_theme.palette().success)
                                        } else {
                                            None
                                        })
                                        .width(Length::Fill),
                                )
                                .push(d.battery.map(Self::battery_level))
                                .push(
                                    icon_button(StaticIcon::Remove)
                                        .on_press(Message::RemoveDevice(d.path.clone()))
                                        .color(theme.iced_theme.palette().danger)
                                        .size(ButtonSize::Small),
                                )
                                .align_y(Vertical::Center)
                                .spacing(theme.space.xs)
                                .width(Length::Fill),
                        ))
                        .on_press(if d.connected {
                            Message::DisconnectDevice(d.path.clone())
                        } else {
                            Message::ConnectDevice(d.path.clone())
                        })
                        .width(Length::Fill)
                        .into()
                    };

                    Some(
                        column!(
                            column!(
                                container(
                                    text(t!("settings-bluetooth-known-devices"))
                                        .size(theme.font_size.xs)
                                        .width(Length::Fill)
                                        .align_x(Horizontal::Right)
                                )
                                .padding([0.0, theme.space.sm]),
                                divider(),
                            ),
                            container(
                                scrollable(Column::with_children(
                                    known_devices.map(known_device_entry),
                                ))
                                .spacing(theme.space.xs)
                            )
                            .max_height(150),
                        )
                        .spacing(theme.space.xs),
                    )
                } else {
                    None
                })
                .push(if some_available {
                    Some(
                        column!(
                            column!(
                                container(
                                    text(t!("settings-bluetooth-available"))
                                        .width(Length::Fill)
                                        .align_x(Horizontal::Right)
                                        .size(theme.font_size.xs),
                                )
                                .padding([0.0, theme.space.sm]),
                                divider(),
                            ),
                            container(
                                scrollable(
                                    Column::with_children(available_devices.map(|d| {
                                        styled_button(Element::from(
                                            row![
                                                text(d.name.clone()).width(Length::Fill),
                                                text(t!("settings-bluetooth-pair"))
                                                    .size(theme.font_size.xs),
                                            ]
                                            .align_y(Vertical::Center)
                                            .spacing(theme.space.xs),
                                        ))
                                        .on_press(Message::PairDevice(d.path.clone()))
                                        .width(Length::Fill)
                                        .into()
                                    }))
                                    .padding(Padding::default().right(theme.space.xs))
                                )
                                .spacing(theme.space.xs)
                            )
                            .max_height(150),
                        )
                        .spacing(theme.space.xs),
                    )
                } else {
                    None
                })
                .push(if !some_known && !some_available {
                    Some(text(t!("settings-bluetooth-no-devices")))
                } else {
                    None
                })
                .push(self.config.more_cmd.as_ref().map(|_| divider()))
                .push(self.config.more_cmd.as_ref().map(|_| {
                    styled_button(t!("settings-more"))
                        .on_press(Message::More(id))
                        .width(Length::Fill)
                }))
                .spacing(theme.space.sm)
                .into()
        })
    }

    fn battery_level<'a>(battery: u8) -> Element<'a, Message> {
        let space = use_theme(|t| t.space);
        container(
            row!(
                icon(match battery {
                    0..=20 => StaticIcon::Battery0,
                    21..=40 => StaticIcon::Battery1,
                    41..=60 => StaticIcon::Battery2,
                    61..=80 => StaticIcon::Battery3,
                    _ => StaticIcon::Battery4,
                }),
                text(format!("{battery}%"))
            )
            .spacing(space.xs)
            .width(Length::Shrink),
        )
        .style(move |theme: &Theme| container::Style {
            text_color: Some(if battery <= 20 {
                theme.palette().danger
            } else {
                theme.palette().text
            }),
            ..container::Style::default()
        })
        .into()
    }

    pub fn bluetooth_indicator<'a>(&'a self) -> Option<Element<'a, Message>> {
        if let Some(service) = &self.service
            && service.state == BluetoothState::Active
        {
            let connected_count = service.devices.iter().filter(|d| d.connected).count();

            if connected_count > 0 {
                Some(
                    format_indicator(
                        self.config.indicator_format,
                        StaticIcon::BluetoothConnected,
                        text(format!("{}", connected_count)).into(),
                        IndicatorState::Normal,
                    )
                    .on_right_press(Message::OpenMore)
                    .into(),
                )
            } else {
                Some(
                    MouseArea::new(icon(StaticIcon::Bluetooth))
                        .on_right_press(Message::OpenMore)
                        .into(),
                )
            }
        } else {
            None
        }
    }

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

    pub fn connected_devices_for_tooltip(&self) -> Vec<(String, Option<u8>)> {
        self.service
            .as_ref()
            .map(|service| {
                service
                    .devices
                    .iter()
                    .filter(|d| d.connected)
                    .map(|d| (d.name.clone(), d.battery))
                    .collect()
            })
            .unwrap_or_default()
    }
}
