use crate::gtk::prelude::ListModelExtManual;
use crate::widget::{
    unit_list::UnitCuratedList, unit_properties_selector::data_selection::UnitPropertySelection,
};
use serde::{Deserialize, Serialize};
use std::{
    env,
    fmt::Display,
    fs::{self, File},
    io::Write,
    path::Path,
};
use tracing::{debug, error, info, warn};

const UNIT_COLUMNS: &str = "unit_columns.toml";

#[derive(Serialize, Deserialize, Debug)]
#[serde(default)]
pub struct UnitColumn {
    pub id: String,
    pub title: Option<String>,
    #[serde(rename = "width")]
    pub fixed_width: i32,
    pub expands: bool,
    pub resizable: bool,
    pub visible: bool,
    #[serde(rename = "type")]
    pub prop_type: Option<String>,
    pub sort: Option<SortType>,
}

impl Default for UnitColumn {
    fn default() -> Self {
        Self {
            id: "".to_owned(),
            title: None,
            fixed_width: -1,
            expands: false,
            resizable: false,
            visible: true,
            prop_type: None,
            sort: None,
        }
    }
}

impl UnitColumn {
    pub fn from(data: &UnitPropertySelection) -> Self {
        Self {
            id: data.id().map(|s| s.to_string()).unwrap_or_default(),
            title: data.title().map(|s| s.to_string()),
            fixed_width: data.fixed_width(),
            expands: data.expands(),
            resizable: data.resizable(),
            visible: data.visible(),
            prop_type: data.prop_type(),
            sort: None,
        }
    }

    pub(crate) fn new(id: &str, arg: &str) -> Self {
        Self {
            id: id.to_owned(),
            prop_type: Some(arg.to_owned()),
            ..Default::default()
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "SortType")]
pub enum SortType {
    #[default]
    Unset,
    Asc,
    Desc,
}

impl Display for SortType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match &self {
            SortType::Asc => "Asc",
            SortType::Desc => "Desc",
            SortType::Unset => "",
        };
        write!(f, "{s}")
    }
}

impl From<SortType> for Option<gtk::SortType> {
    fn from(val: SortType) -> Self {
        match val {
            SortType::Unset => None,
            SortType::Asc => Some(gtk::SortType::Ascending),
            SortType::Desc => Some(gtk::SortType::Descending),
        }
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct MyConfig {
    #[serde(rename = "column")]
    pub columns: Vec<UnitColumn>,
}

impl MyConfig {
    pub fn is_empty(&self) -> bool {
        self.columns.is_empty()
    }
}

pub fn save_column_config(
    columns: Option<&gio::ListModel>,
    data: &mut [UnitPropertySelection],
    view: UnitCuratedList,
) {
    order_columns(columns, data);

    let data_list: Vec<UnitColumn> = data.iter().map(UnitColumn::from).collect();
    let config = MyConfig { columns: data_list };

    let sysd_manager_config_dir = get_sysd_manager_config_dir();

    if let Err(e) = fs::create_dir_all(&sysd_manager_config_dir) {
        error!(
            "Failed to create config directory {:?}: {}",
            sysd_manager_config_dir, e
        );
        return;
    }

    let file_name = file_name(view);
    let config_path = sysd_manager_config_dir.join(file_name);

    if let Err(e) = save_to_toml_file(&config, &config_path) {
        error!(
            "Failed to save column config to TOML file: {:?} {:?}",
            config_path, e
        );
    } else {
        info!("Column config saved to {:?}", config_path);
    }
}

fn file_name(view: UnitCuratedList) -> String {
    match view {
        UnitCuratedList::Custom => UNIT_COLUMNS.to_owned(),
        _ => format!("{}_{}", view.id(), UNIT_COLUMNS),
    }
}

pub fn order_columns(columns: Option<&gio::ListModel>, data: &mut [UnitPropertySelection]) {
    let Some(columns) = columns else {
        return;
    };

    let ids: Vec<_> = columns
        .iter::<gtk::ColumnViewColumn>()
        .filter_map(|result| result.inspect_err(|err| warn!("Error: {err:?}")).ok())
        .filter_map(|column| column.id())
        .collect();

    if ids.len() != data.len() {
        warn!("Lenght not in sync");
        return;
    }

    for (index, id) in ids.iter().enumerate() {
        let mut index_op: Option<usize> = None;

        // for sub_index in index..data.len() {
        for (sub_index, ps) in data.iter().enumerate().skip(index) {
            let Some(sub_id) = ps.id() else {
                continue;
            };

            debug!("-- {index} {id} -- {sub_index} {sub_id} ");

            if *id == sub_id {
                if index != sub_index {
                    index_op = Some(sub_index);
                }
                break;
            }
        }

        if let Some(index_op) = index_op {
            debug!("Swap {id} {index} {index_op} ");
            data.swap(index, index_op);
        }
    }
}

pub(crate) fn get_sysd_manager_config_dir() -> std::path::PathBuf {
    let xdg_config_home = get_xdg_config_home();

    Path::new(&xdg_config_home).join("sysd-manager")
}

fn get_xdg_config_home() -> String {
    env::var("XDG_CONFIG_HOME").unwrap_or_else(|_| {
        let home = env::var("HOME").unwrap_or_else(|_| ".".to_string());
        format!("{}/.config", home)
    })
}

pub(crate) fn save_to_toml_file<T>(data: &T, path: &Path) -> std::io::Result<()>
where
    T: Serialize,
{
    let toml_str = toml::to_string_pretty(data).expect("Failed to serialize data to TOML");
    let mut file = File::create(path)?;
    file.write_all(toml_str.as_bytes())?;
    Ok(())
}

pub fn load_column_config(view: UnitCuratedList) -> Option<MyConfig> {
    let sysd_manager_config_dir = get_sysd_manager_config_dir();

    if !sysd_manager_config_dir.exists() {
        info!(
            "Config directory {:?} does not exist. Using default configuration.",
            sysd_manager_config_dir
        );
        return None;
    }

    let file_name = file_name(view);

    let config_path = sysd_manager_config_dir.join(file_name);

    if !config_path.exists() {
        info!(
            "Config file {:?} does not exist. Using default configuration.",
            config_path
        );
        return None;
    }

    match fs::read_to_string(&config_path) {
        Ok(toml_str) => match toml::from_str::<MyConfig>(&toml_str) {
            Ok(config) => {
                if config.is_empty() {
                    warn!("Loaded config is empty, FALLBACK on default");
                    None
                } else {
                    Some(config)
                }
            }
            Err(e) => {
                error!("Failed to parse TOML from {:?}: {}", config_path, e);
                None
            }
        },
        Err(e) => {
            error!("Failed to read config file {:?}: {}", config_path, e);
            None
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_save_array_of_structs_to_toml() {
        let data_list = vec![
            UnitColumn {
                id: "alpha".to_string(),
                title: Some("Alpha Title".to_string()),
                fixed_width: 1,
                expands: true,
                resizable: false,
                visible: true,
                ..Default::default()
            },
            UnitColumn {
                id: "beta".to_string(),
                title: Some("Beta Title".to_string()),
                fixed_width: 2,
                expands: false,
                resizable: true,
                visible: false,
                ..Default::default()
            },
            UnitColumn {
                id: "gamma".to_string(),
                title: Some("Gamma Title".to_string()),
                fixed_width: 3,
                expands: true,
                resizable: true,
                visible: true,
                prop_type: Some("i".to_string()),
                sort: Some(SortType::Asc),
            },
            UnitColumn {
                id: "".to_string(),
                title: None,
                fixed_width: 3,
                expands: true,
                resizable: true,
                visible: true,
                ..Default::default()
            },
        ];

        let config = MyConfig { columns: data_list };

        let toml_str = toml::to_string_pretty(&config).expect("Failed to serialize array to TOML");

        println!("{}", toml_str);

        // Check that each struct appears as a TOML table
        assert!(toml_str.contains("id = \"alpha\""));
        assert!(toml_str.contains("id = \"beta\""));
        assert!(toml_str.contains("title = \"Gamma Title\""));
        assert!(toml_str.matches("[").count() >= 4); // At least 4 tables
    }

    #[test]
    fn test_toml_save_empty() {
        let data_list = vec![];

        let config = MyConfig { columns: data_list };

        let toml_str = toml::to_string_pretty(&config).expect("Failed to serialize array to TOML");

        println!("{}", toml_str);
    }

    #[test]
    fn test_load_multiple_structs_from_toml_file() {
        let toml_content = r#"
            [[column]]
            id = "alpha"
            title = "Alpha Title"
            fixed_width = 1
            expands = true
            resizable = false
            visible = true

            [[column]]
            id = "beta"
            title = "Beta Title"
            width = 2
            expands = false
            resizable = true
            visible = false

            [[column]]
            id = "gamma"
            title = "Gamma Title"
            width = 3
            expands = true
            resizable = true
            visible = true

            [[column]]
            expands = true
            resizable = true
            visible = true
            type = "i"
        "#;

        let config: MyConfig = toml::from_str(toml_content).expect("Failed to parse TOML");

        assert!(config.columns.len() >= 4);
        assert_eq!(config.columns[0].id.as_str(), "alpha");
        assert_eq!(config.columns[1].fixed_width, 2);
        assert!(config.columns[2].visible);
        assert_eq!(config.columns[3].title, None);
        assert_eq!(config.columns[3].fixed_width, -1);
        assert_eq!(config.columns[3].prop_type, Some("i".to_string()));
    }

    #[test]
    fn test_iter() {
        let data = ['a', 'b', 'c', 'd'];

        for (sub_index, ps) in data[2..].iter().enumerate() {
            println!("{sub_index} {ps}")
        }
    }
}
