use core::fmt::Write;

use embedded_graphics::{
    pixelcolor::Gray8,
    prelude::{DrawTarget, OriginDimensions},
};
use heapless::String;

use crate::{
    input::InputEvent,
    ui::app::AppCommand,
    wifi::{WifiAccessPoint, WifiEvent, WifiNetworkList, WifiPassword, WifiSsid},
};

use super::ScreenUpdate;
use crate::ui::{frame::Frame, RefreshHint};

const PASSWORD_CHARS: &[u8] =
    b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .-_!?@#$%&*+/=:";

pub(crate) struct OnboardingScreen {
    state: OnboardingState,
}

enum OnboardingState {
    ScanIntro { notice: &'static str },
    Scanning,
    NetworkList(NetworkListState),
    PasswordEntry(PasswordEntryState),
    Joining(JoiningState),
    SavedWifiConnecting { ssid: WifiSsid },
    Joined { ssid: WifiSsid },
}

struct NetworkListState {
    networks: WifiNetworkList,
    selected: usize,
    notice: &'static str,
}

struct PasswordEntryState {
    networks: WifiNetworkList,
    selected: usize,
    password: WifiPassword,
    char_index: usize,
    notice: &'static str,
}

struct JoiningState {
    networks: WifiNetworkList,
    selected: usize,
    password: WifiPassword,
}

#[derive(Clone, Copy)]
pub(crate) enum OnboardingAction {
    None,
    OpenLibrary,
}

impl OnboardingScreen {
    pub fn new() -> Self {
        Self {
            state: OnboardingState::ScanIntro { notice: "" },
        }
    }

    pub fn new_connecting_saved(ssid: WifiSsid) -> Self {
        Self {
            state: OnboardingState::SavedWifiConnecting { ssid },
        }
    }

    pub fn update(&mut self, event: InputEvent) -> ScreenUpdate<OnboardingAction> {
        match &mut self.state {
            OnboardingState::ScanIntro { .. } => match event {
                InputEvent::Select => {
                    self.state = OnboardingState::Scanning;
                    ScreenUpdate::with_command(
                        true,
                        RefreshHint::Full,
                        OnboardingAction::None,
                        AppCommand::ScanWifi,
                    )
                }
                _ => ScreenUpdate::new(false, RefreshHint::None, OnboardingAction::None),
            },
            OnboardingState::Scanning => {
                ScreenUpdate::new(false, RefreshHint::None, OnboardingAction::None)
            }
            OnboardingState::NetworkList(state) => match event {
                InputEvent::Up => {
                    state.selected = if state.selected == 0 {
                        state.networks.len() - 1
                    } else {
                        state.selected - 1
                    };
                    state.notice = "";
                    ScreenUpdate::new(true, RefreshHint::Fast, OnboardingAction::None)
                }
                InputEvent::Down => {
                    state.selected = (state.selected + 1) % state.networks.len();
                    state.notice = "";
                    ScreenUpdate::new(true, RefreshHint::Fast, OnboardingAction::None)
                }
                InputEvent::Select => {
                    let access_point = state.networks[state.selected].clone();
                    if access_point.is_locked() {
                        self.state = OnboardingState::PasswordEntry(PasswordEntryState::new(
                            state.networks.clone(),
                            state.selected,
                        ));
                        ScreenUpdate::new(true, RefreshHint::Full, OnboardingAction::None)
                    } else {
                        let password = WifiPassword::new();
                        self.state = OnboardingState::Joining(JoiningState {
                            networks: state.networks.clone(),
                            selected: state.selected,
                            password: password.clone(),
                        });
                        ScreenUpdate::with_command(
                            true,
                            RefreshHint::Full,
                            OnboardingAction::None,
                            AppCommand::JoinWifi {
                                access_point,
                                password,
                            },
                        )
                    }
                }
                InputEvent::Back | InputEvent::SelectLong => {
                    self.state = OnboardingState::Scanning;
                    ScreenUpdate::with_command(
                        true,
                        RefreshHint::Full,
                        OnboardingAction::None,
                        AppCommand::ScanWifi,
                    )
                }
                _ => ScreenUpdate::new(false, RefreshHint::None, OnboardingAction::None),
            },
            OnboardingState::PasswordEntry(state) => match event {
                InputEvent::Up => {
                    state.previous_char();
                    ScreenUpdate::new(true, RefreshHint::Fast, OnboardingAction::None)
                }
                InputEvent::Down => {
                    state.next_char();
                    ScreenUpdate::new(true, RefreshHint::Fast, OnboardingAction::None)
                }
                InputEvent::Select => {
                    state.append_char();
                    ScreenUpdate::new(true, RefreshHint::Fast, OnboardingAction::None)
                }
                InputEvent::Back => {
                    state.delete_char();
                    ScreenUpdate::new(true, RefreshHint::Fast, OnboardingAction::None)
                }
                InputEvent::SelectLong => {
                    if state.can_join() {
                        let access_point = state.access_point().clone();
                        let password = state.password.clone();
                        self.state = OnboardingState::Joining(JoiningState {
                            networks: state.networks.clone(),
                            selected: state.selected,
                            password: password.clone(),
                        });
                        ScreenUpdate::with_command(
                            true,
                            RefreshHint::Full,
                            OnboardingAction::None,
                            AppCommand::JoinWifi {
                                access_point,
                                password,
                            },
                        )
                    } else {
                        state.notice = "Password must be 8-63 characters";
                        ScreenUpdate::new(true, RefreshHint::Fast, OnboardingAction::None)
                    }
                }
                InputEvent::BackLong => {
                    self.state = OnboardingState::NetworkList(NetworkListState {
                        networks: state.networks.clone(),
                        selected: state.selected,
                        notice: "",
                    });
                    ScreenUpdate::new(true, RefreshHint::Full, OnboardingAction::None)
                }
            },
            OnboardingState::Joining(_) | OnboardingState::SavedWifiConnecting { .. } => {
                ScreenUpdate::new(false, RefreshHint::None, OnboardingAction::None)
            }
            OnboardingState::Joined { .. } => match event {
                InputEvent::Select => {
                    ScreenUpdate::new(true, RefreshHint::Full, OnboardingAction::OpenLibrary)
                }
                InputEvent::Back => {
                    self.state = OnboardingState::ScanIntro { notice: "" };
                    ScreenUpdate::new(true, RefreshHint::Full, OnboardingAction::None)
                }
                _ => ScreenUpdate::new(false, RefreshHint::None, OnboardingAction::None),
            },
        }
    }

    pub fn handle_wifi_event(&mut self, event: WifiEvent) -> ScreenUpdate<OnboardingAction> {
        match event {
            WifiEvent::ScanFinished(Ok(networks)) => {
                if networks.is_empty() {
                    self.state = OnboardingState::ScanIntro {
                        notice: "No networks found",
                    };
                } else {
                    self.state = OnboardingState::NetworkList(NetworkListState {
                        networks,
                        selected: 0,
                        notice: "",
                    });
                }
                ScreenUpdate::new(true, RefreshHint::Full, OnboardingAction::None)
            }
            WifiEvent::ScanFinished(Err(error)) => {
                self.state = OnboardingState::ScanIntro {
                    notice: error.message(),
                };
                ScreenUpdate::new(true, RefreshHint::Full, OnboardingAction::None)
            }
            WifiEvent::JoinFinished(Ok(ssid)) => {
                let action = if matches!(self.state, OnboardingState::SavedWifiConnecting { .. }) {
                    OnboardingAction::OpenLibrary
                } else {
                    OnboardingAction::None
                };
                self.state = OnboardingState::Joined { ssid };
                ScreenUpdate::new(true, RefreshHint::Full, action)
            }
            WifiEvent::JoinFinished(Err(error)) => {
                let previous =
                    core::mem::replace(&mut self.state, OnboardingState::ScanIntro { notice: "" });

                self.state = match previous {
                    OnboardingState::Joining(state) => {
                        OnboardingState::PasswordEntry(PasswordEntryState {
                            networks: state.networks,
                            selected: state.selected,
                            password: state.password,
                            char_index: 0,
                            notice: error.message(),
                        })
                    }
                    _ => OnboardingState::ScanIntro {
                        notice: error.message(),
                    },
                };

                ScreenUpdate::new(true, RefreshHint::Full, OnboardingAction::None)
            }
        }
    }

    pub fn render<D>(&self, frame: &mut Frame<'_, D>) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        match &self.state {
            OnboardingState::ScanIntro { notice } => self.render_scan_intro(frame, notice),
            OnboardingState::Scanning => self.render_scanning(frame),
            OnboardingState::NetworkList(state) => self.render_network_list(frame, state),
            OnboardingState::PasswordEntry(state) => self.render_password_entry(frame, state),
            OnboardingState::Joining(state) => self.render_joining(frame, state),
            OnboardingState::SavedWifiConnecting { ssid } => {
                self.render_saved_wifi_connecting(frame, ssid)
            }
            OnboardingState::Joined { ssid } => self.render_joined(frame, ssid),
        }
    }

    fn render_scan_intro<D>(
        &self,
        frame: &mut Frame<'_, D>,
        notice: &'static str,
    ) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        frame.title("WiFi setup")?;
        frame.body(86, "Connect this device before loading books.")?;
        frame.body(122, "Press select to scan for nearby networks.")?;
        if !notice.is_empty() {
            frame.small(174, notice)?;
        }
        frame.footer("Select: scan")
    }

    fn render_scanning<D>(&self, frame: &mut Frame<'_, D>) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        frame.title("Scanning")?;
        frame.body(96, "Looking for WiFi networks...")?;
        frame.small(148, "This can take a few seconds.")?;
        frame.footer("Please wait")
    }

    fn render_network_list<D>(
        &self,
        frame: &mut Frame<'_, D>,
        state: &NetworkListState,
    ) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        frame.title("Choose network")?;

        let mut count_line = String::<48>::new();
        let _ = write!(count_line, "{} networks found", state.networks.len());
        frame.small(58, count_line.as_str())?;

        let visible_rows = 8;
        let first_row = if state.selected >= visible_rows {
            state.selected + 1 - visible_rows
        } else {
            0
        };
        let last_row = (first_row + visible_rows).min(state.networks.len());

        let mut y = 100;
        for index in first_row..last_row {
            let network = &state.networks[index];
            let marker = if index == state.selected { ">" } else { " " };
            let mut row = String::<128>::new();
            let _ = write!(
                row,
                "{} {}  {}  {}",
                marker,
                network.ssid.as_str(),
                network.signal_label(),
                network.security.label()
            );
            frame.body(y, row.as_str())?;
            y += 34;
        }

        if state.networks.len() > visible_rows {
            let mut range_line = String::<48>::new();
            let _ = write!(range_line, "Showing {}-{}", first_row + 1, last_row);
            frame.small(380, range_line.as_str())?;
        }

        if !state.notice.is_empty() {
            frame.small(400, state.notice)?;
        }

        frame.footer("Up/Down move Select choose Back rescan")
    }

    fn render_password_entry<D>(
        &self,
        frame: &mut Frame<'_, D>,
        state: &PasswordEntryState,
    ) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let access_point = state.access_point();
        let mut network_line = String::<96>::new();
        let _ = write!(network_line, "Network: {}", access_point.ssid.as_str());

        frame.title("WiFi password")?;
        frame.body(66, network_line.as_str())?;
        frame.body(112, "Password:")?;
        if state.password.is_empty() {
            frame.body(146, "<empty>")?;
        } else {
            frame.body(146, state.password.as_str())?;
        }

        frame.body(204, "Current char:")?;
        let mut char_line = String::<24>::new();
        if state.current_char() == ' ' {
            let _ = write!(char_line, "[ space ]");
        } else {
            let _ = write!(char_line, "[ {} ]", state.current_char());
        }
        frame.body(238, char_line.as_str())?;

        if !state.notice.is_empty() {
            frame.small(292, state.notice)?;
        }

        frame.footer("Up/Down char Select add Back del Hold Select join")
    }

    fn render_joining<D>(
        &self,
        frame: &mut Frame<'_, D>,
        state: &JoiningState,
    ) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let network = &state.networks[state.selected];
        let mut network_line = String::<96>::new();
        let _ = write!(network_line, "Joining {}", network.ssid.as_str());

        frame.title("Joining")?;
        frame.body(92, network_line.as_str())?;
        frame.body(136, "Waiting for the access point...")?;
        frame.footer("Please wait")
    }

    fn render_saved_wifi_connecting<D>(
        &self,
        frame: &mut Frame<'_, D>,
        ssid: &WifiSsid,
    ) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let mut network_line = String::<96>::new();
        let _ = write!(network_line, "Connecting to {}", ssid.as_str());

        frame.title("WiFi")?;
        frame.body(92, network_line.as_str())?;
        frame.body(136, "Using saved credentials...")?;
        frame.footer("Please wait")
    }

    fn render_joined<D>(&self, frame: &mut Frame<'_, D>, ssid: &WifiSsid) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let mut network_line = String::<96>::new();
        let _ = write!(network_line, "Connected to {}", ssid.as_str());

        frame.title("Connected")?;
        frame.body(92, network_line.as_str())?;
        frame.body(136, "Next screen: library.")?;
        frame.footer("Select continue Back scan again")
    }
}

impl PasswordEntryState {
    fn new(networks: WifiNetworkList, selected: usize) -> Self {
        Self {
            networks,
            selected,
            password: WifiPassword::new(),
            char_index: 0,
            notice: "",
        }
    }

    fn access_point(&self) -> &WifiAccessPoint {
        &self.networks[self.selected]
    }

    fn previous_char(&mut self) {
        self.notice = "";
        self.char_index = if self.char_index == 0 {
            PASSWORD_CHARS.len() - 1
        } else {
            self.char_index - 1
        };
    }

    fn next_char(&mut self) {
        self.notice = "";
        self.char_index = (self.char_index + 1) % PASSWORD_CHARS.len();
    }

    fn append_char(&mut self) {
        self.notice = "";
        if self.password.push(self.current_char()).is_err() {
            self.notice = "Password is full";
        }
    }

    fn delete_char(&mut self) {
        self.notice = "";
        if self.password.pop().is_none() {
            self.notice = "Nothing to delete";
        }
    }

    fn can_join(&self) -> bool {
        !self.access_point().is_locked() || (self.password.len() >= 8 && self.password.len() <= 63)
    }

    fn current_char(&self) -> char {
        char::from(PASSWORD_CHARS[self.char_index])
    }
}
