use ratatui::style::Color;
use toml;

use dirs;
use serde::Deserialize;

#[derive(Deserialize, Debug)]
pub struct Config {
    #[serde(default = "default_switch_mode")]
    pub switch: char,

    #[serde(default = "default_device_mode")]
    pub mode: String,

    #[serde(default = "default_esc_quit")]
    pub esc_quit: bool,

    #[serde(default = "default_ascii")]
    pub ascii: bool,

    #[serde(default)]
    pub device: Device,

    #[serde(default)]
    pub station: Station,

    #[serde(default)]
    pub ap: AccessPoint,

    #[serde(default)]
    pub theme: Theme,
}

fn default_switch_mode() -> char {
    'r'
}

fn default_device_mode() -> String {
    "station".to_string()
}

fn default_esc_quit() -> bool {
    false
}

fn default_ascii() -> bool {
    false
}

// Device
#[derive(Deserialize, Debug)]
pub struct Device {
    #[serde(default = "default_show_device_infos")]
    pub infos: char,
    pub toggle_power: char,
}

impl Default for Device {
    fn default() -> Self {
        Self {
            infos: 'i',
            toggle_power: 'o',
        }
    }
}

fn default_show_device_infos() -> char {
    'i'
}

// Station
#[derive(Deserialize, Debug)]
pub struct Station {
    #[serde(default = "default_station_start_scanning")]
    pub start_scanning: char,

    #[serde(default)]
    pub known_network: KnownNetwork,

    #[serde(default)]
    pub new_network: NewNetwork,
}

impl Default for Station {
    fn default() -> Self {
        Self {
            start_scanning: 's',
            known_network: KnownNetwork::default(),
            new_network: NewNetwork::default(),
        }
    }
}

fn default_station_start_scanning() -> char {
    's'
}

#[derive(Deserialize, Debug)]
pub struct KnownNetwork {
    #[serde(default = "default_station_remove_known_network")]
    pub remove: char,
    pub toggle_autoconnect: char,
    pub show_all: char,
    pub share: char,
}

impl Default for KnownNetwork {
    fn default() -> Self {
        Self {
            remove: 'd',
            toggle_autoconnect: 't',
            show_all: 'a',
            share: 'p',
        }
    }
}

fn default_station_remove_known_network() -> char {
    'd'
}

#[derive(Deserialize, Debug)]
pub struct NewNetwork {
    pub show_all: char,
    pub connect_hidden: char,
}

impl Default for NewNetwork {
    fn default() -> Self {
        Self {
            show_all: 'a',
            connect_hidden: 'n',
        }
    }
}

// Access Point
#[derive(Deserialize, Debug)]
pub struct AccessPoint {
    #[serde(default = "default_ap_start")]
    pub start: char,

    #[serde(default = "default_ap_stop")]
    pub stop: char,
}

impl Default for AccessPoint {
    fn default() -> Self {
        Self {
            start: 'n',
            stop: 'x',
        }
    }
}

fn default_ap_start() -> char {
    'n'
}

fn default_ap_stop() -> char {
    'x'
}

// Theme
#[derive(Deserialize, Debug)]
pub struct Theme {
    #[serde(default = "default_background")]
    pub background: Color,

    #[serde(default = "default_border")]
    pub border: Color,

    #[serde(default = "default_text_color")]
    pub text_color: Color,

    #[serde(default = "default_hidden_color")]
    pub hidden_color: Color,

    #[serde(default = "default_info_color")]
    pub info_color: Color,

    #[serde(default = "default_warning_color")]
    pub warning_color: Color,

    #[serde(default = "default_error_color")]
    pub error_color: Color,
}

fn default_background() -> Color {
    Color::DarkGray
}

fn default_border() -> Color {
    Color::Green
}

fn default_text_color() -> Color {
    Color::White
}

fn default_hidden_color() -> Color {
    Color::DarkGray
}

fn default_info_color() -> Color {
    Color::Green
}

fn default_warning_color() -> Color {
    Color::Yellow
}

fn default_error_color() -> Color {
    Color::Red
}

impl Default for Theme {
    fn default() -> Self {
        Self {
            background: default_background(),
            border: default_border(),
            text_color: default_text_color(),
            hidden_color: default_hidden_color(),
            info_color: default_info_color(),
            warning_color: default_warning_color(),
            error_color: default_error_color(),
        }
    }
}

impl Config {
    pub fn new() -> anyhow::Result<Self> {
        let conf_path = dirs::config_dir()
            .unwrap()
            .join("impala")
            .join("config.toml");

        let config = std::fs::read_to_string(&conf_path).unwrap_or_default();
        let app_config: Config = toml::from_str(&config)
            .map_err(|e| anyhow::anyhow!("Invalid config at {}: {e}", conf_path.display()))?;

        Ok(app_config)
    }
}
