use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::anyhow;
use i_slint_core::api::{Image, ToSharedString};
use log::{debug, error, info, trace, warn};
use serde::Deserialize;
use serde_aux::prelude::*;
use walkdir::WalkDir;
use crate::LoginOptionTileData;

#[derive(Deserialize, Debug)]
pub struct LoginOptionTileConfig {
    pub(crate) image_path: Option<PathBuf>,
    pub(crate) command: String,
    #[serde(default = "default_i32::<-1>")]
    pub(crate) index: i32,
    #[serde(default)]
    pub(crate) is_default: bool
}

pub fn default_cache_dir() -> PathBuf {
    #[cfg(debug_assertions)]
    return "./cache/ckgreeter".into();
    #[cfg(not(debug_assertions))]
    return "/var/cache/ckgreeter".into();
}

#[derive(Deserialize)]
pub struct Config {
    pub(crate) default_username: Option<String>,
    #[serde(default)]
    pub(crate) environments: HashMap<String, LoginOptionTileConfig>,
    pub(crate) pexels_api_key: Option<String>,
    pub(crate) pexels_query: Option<String>,
    #[serde(default = "default_cache_dir", alias = "bg_cache_directory")]
    pub(crate) background_cache_directory: PathBuf,
    #[serde(default = "default_i32::<30>", alias = "bg_delay")]
    pub(crate) background_delay: i32,
    #[serde(default = "bool_true")]
    pub(crate) find_wayland_sessions: bool,
    #[serde(default = "bool_true")]
    pub(crate) find_x_sessions: bool,
    pub(crate) default_by_name: Option<String>,
}

impl Config {
    pub fn get_tiles(&mut self) -> anyhow::Result<(Vec<LoginOptionTileData>, Option<usize>)> {
        debug!(target: "ckgreeter::config_get_tiles", "Getting tiles from config");

        fn get_sessions_from_dir(dir: &Path) -> anyhow::Result<HashMap<String, LoginOptionTileConfig>> {
            let mut ret = HashMap::new();
            for entry in std::fs::read_dir(dir)? {
                if let Err(e) = entry {
                    error!("Failed to read directory: {}", e);
                    continue
                }
                let entry = entry?;
                let file_type = entry.file_type()?;
                if !file_type.is_file() {
                    continue;
                }
                let path = entry.path();
                if path.extension().and_then(|s| s.to_str()) == Some("desktop") {
                    let desktop_entry = freedesktop_entry_parser::parse_entry(path)?;
                    let desktop_section = desktop_entry.section("Desktop Entry").expect("Desktop Entry must have a Desktop Entry section");
                    let session_name = desktop_section
                        .attr("Name").first()
                        .expect("Desktop Entry must have a Name attribute");
                    let session_exec = desktop_section
                        .attr("Exec").first()
                        .expect("Desktop Entry must have a Exec attribute");
                    if let Some(session_try_exec) = desktop_section.attr("TryExec").first()
                    {
                        let try_exec_path = Path::new(session_try_exec);
                        if !try_exec_path.exists() || !try_exec_path.is_file() || !is_executable::is_executable(try_exec_path) {
                            continue;
                        }
                    }
                    let desktop_names = desktop_section.attr("DesktopNames");
                    let mut candidate_svg_paths = Vec::new();
                    let mut candidate_png_paths = Vec::new();

                    for entry in WalkDir::new("/usr/share/icons/").follow_links(true)
                    {
                        if let Err(e) = entry {
                            error!("Failed to walk /usr/share/icons/: {}", e);
                            continue
                        }
                        let entry = entry?;
                        if !entry.file_type().is_file() {
                            continue
                        }
                        let path = entry.path().to_owned();
                        let file_name = path.file_stem().and_then(|s| s.to_str());
                        let extension = path.extension().and_then(|s| s.to_str());

                        if file_name.is_none() || extension.is_none() {
                            continue
                        }

                        let file_name = file_name.unwrap();
                        let extension = extension.unwrap();

                        if extension != "svg" && extension != "png" {
                            continue
                        }

                        if desktop_names.iter().any(|name| name.eq_ignore_ascii_case(file_name)) {
                            match extension {
                                "png" => candidate_png_paths.push(path),
                                "svg" => candidate_svg_paths.push(path),
                                _ => {}
                            }
                        }
                    }

                    let mut image_path = None;
                    if candidate_svg_paths.len() > 0 {
                        image_path = Some(candidate_svg_paths[0].to_owned());
                    } else if candidate_png_paths.len() > 0 {
                        image_path = Some(candidate_png_paths[0].to_owned());
                    }

                    ret.insert(session_name.clone(), LoginOptionTileConfig {
                        image_path, command: session_exec.clone(), index: -1, is_default: false
                    });
                }
            }
            Ok(ret)
        }

        let mut x_sessions = if self.find_x_sessions {
            info!(target: "ckgreeter::config_get_tiles", "Adding X sessions from `/usr/share/xsessions/`");
            Some(get_sessions_from_dir(Path::new("/usr/share/xsessions/"))?)
        } else {None};

        let mut wayland_sessions = if self.find_wayland_sessions {
            info!(target: "ckgreeter::config_get_tiles", "Adding Wayland sessions from `/usr/share/wayland-sessions/`");
            Some(get_sessions_from_dir(Path::new("/usr/share/wayland-sessions/"))?)
        } else {None};

        if let Some(x_sessions) = &mut x_sessions && let Some(wayland_sessions) = &mut wayland_sessions {
            let mut duplicate_sessions = Vec::new();
            for session_name in x_sessions.keys() {
                if wayland_sessions.contains_key(session_name) {
                    duplicate_sessions.push(session_name.clone());
                }
            }

            if duplicate_sessions.len() > 0 {
                warn!(target: "ckgreeter::config_get_tiles", "Found duplicate sessions in Wayland and X11 sessions: {:?}", duplicate_sessions);
                warn!(target: "ckgreeter::config_get_tiles", "Adding X11 and Wayland name ends to fix.");

                for session_name in duplicate_sessions {
                    let old_x = x_sessions.remove(&session_name).unwrap();
                    let old_wl = wayland_sessions.remove(&session_name).unwrap();
                    x_sessions.insert(format!("{} (X11)", session_name), old_x);
                    wayland_sessions.insert(format!("{} (Wayland)", session_name), old_wl);
                }
            }
        }

        if let Some(x_sessions) = x_sessions {
            self.environments.extend(x_sessions);
        }
        if let Some(wayland_sessions) = wayland_sessions {
            self.environments.extend(wayland_sessions);
        }


        let mut ordered = self
            .environments
            .iter()
            .collect::<Vec<(&String, &LoginOptionTileConfig)>>();

        debug!(target: "ckgreeter::config_get_tiles", "Sorting tiles");
        ordered.sort_by_key(|(name, env)| {
            let index = if env.index >= 0 { env.index } else { i32::MAX };
            (index, name.as_str())
        });

        let mut tiles: Vec<LoginOptionTileData> = Vec::new();
        let mut default = None;

        let mut seen_indexes = std::collections::HashSet::new();

        for (name, tile_config) in ordered {
            debug!(target: "ckgreeter::config_get_tiles", "Adding tile `{}`", name);
            trace!(target: "ckgreeter::config_get_tiles", "Tile config: {:#?}", tile_config);
            if tile_config.index >= 0 && !seen_indexes.insert(tile_config.index) {
                return Err(anyhow!(
                    "Duplicate login option index `{}` found near `{}`",
                    tile_config.index,
                    name
                ));
            }
            if tile_config.is_default {
                if default.is_some() {
                    return Err(anyhow!("There can only be one default login option..."));
                }
                debug!(target: "ckgreeter::config_get_tiles", "Setting default tile to `{}`", name);
                default = Some(tiles.len());
            }

            if let Some(image_path) = &tile_config.image_path {
                let image = Image::load_from_path(image_path);
                if let Ok(image) = image {
                    tiles.push(LoginOptionTileData {
                        command: tile_config.command.to_shared_string(),
                        image,
                        name: name.to_shared_string(),
                    })
                } else {
                    error!(target: "ckgreeter::config_get_tiles", "Failed to load image for login option `{}` from `{}`", name, image_path.display());
                    tiles.push(LoginOptionTileData {
                        command: tile_config.command.to_shared_string(),
                        image: Image::default(),
                        name: name.to_shared_string(),
                    })
                }
            } else {
                tiles.push(LoginOptionTileData {
                    command: tile_config.command.to_shared_string(),
                    image: Image::default(),
                    name: name.to_shared_string(),
                })
            }
        }

        if default.is_none() && let Some(default_name) = &self.default_by_name {
            debug!(target: "ckgreeter::config_get_tiles", "Trying to find default tile `{}` (by name)", default_name);
            for (index, tile_config) in tiles.iter().enumerate() {
                if tile_config.name == default_name {
                    if default.is_some() {
                        return Err(anyhow!("There can only be one default login option..."));
                    }
                    debug!(target: "ckgreeter::config_get_tiles", "Setting default tile to `{}`", tile_config.name.as_str());
                    default = Some(index);
                    break
                }
            }
        }

        Ok((tiles, default))
    }
}