use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
pub struct HoardomPaths {
    pub config_file: PathBuf,
    pub cache_dir: PathBuf,
    pub can_save: bool,
    pub caching_enabled: bool,
}

impl HoardomPaths {
    pub fn cache_file(&self, name: &str) -> PathBuf {
        self.cache_dir.join(name)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    #[serde(default)]
    pub settings: Settings,
    #[serde(default)]
    pub cache: CacheSettings,
    #[serde(default)]
    pub favorites: Vec<FavoriteEntry>,
    #[serde(default)]
    pub imported_filters: Vec<ImportedFilter>,
    #[serde(default)]
    pub scratchpad: String,
}

/// faved domain with its last known status where once again too many dumbass comments were added when fixing a bug with it... have been removed
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FavoriteEntry {
    pub domain: String,
    #[serde(default = "default_fav_status")]
    pub status: String,
    #[serde(default)]
    // date string mlol
    pub checked: String,
    #[serde(default)]
    pub changed: bool,
}


fn default_fav_status() -> String {
    "unknown".to_string()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings {
    #[serde(default = "default_tld_list")]
    pub tld_list: String,
    #[serde(default)]
    pub show_all: bool,
    #[serde(default = "default_clear_on_search")]
    pub clear_on_search: bool,
    #[serde(default)]
    pub show_notes_panel: bool,
    #[serde(default)]
    pub last_fav_export_path: String,
    #[serde(default)]
    pub last_res_export_path: String,
    #[serde(default)]
    pub top_tlds: Vec<String>,
    #[serde(default = "default_jobs")]
    pub jobs: u8,
    /// valid ones are : rate_limit, invalid_tld, timeout, unknown and forbidden
    #[serde(default = "default_noretry")]
    pub noretry: Vec<String>,
    #[serde(default = "default_backups_enabled")]
    pub backups: bool,
    #[serde(default = "default_backup_count")]
    pub backup_count: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheSettings {
    #[serde(default)]
    pub last_updated: String,
    /// 0 = stfu about stale cache
    #[serde(default = "default_outdated_cache_days")]
    pub outdated_cache: u32,
    /// auto refresh for cuck cache
    #[serde(default = "default_auto_update")]
    pub auto_update_cache: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportedFilter {
    pub name: String,
    pub tlds: Vec<String>,
}

fn default_tld_list() -> String {
    crate::tlds::default_list_name().to_string()
}

fn default_outdated_cache_days() -> u32 {
    7
}

fn default_auto_update() -> bool {
    true
}

fn default_clear_on_search() -> bool {
    true
}

fn default_jobs() -> u8 {
    32
}

fn default_noretry() -> Vec<String> {
    vec![
        "rate_limit".to_string(),
        "invalid_tld".to_string(),
        "forbidden".to_string(),
    ]
}

fn default_backups_enabled() -> bool {
    true
}

fn default_backup_count() -> u32 {
    32
}

impl Default for Settings {
    fn default() -> Self {
        Self {
            tld_list: default_tld_list(),
            show_all: false,
            clear_on_search: default_clear_on_search(),
            show_notes_panel: false,
            last_fav_export_path: String::new(),
            last_res_export_path: String::new(),
            top_tlds: Vec::new(),
            jobs: default_jobs(),
            noretry: default_noretry(),
            backups: default_backups_enabled(),
            backup_count: default_backup_count(),
        }
    }
}

impl Default for CacheSettings {
    fn default() -> Self {
        Self {
            last_updated: String::new(),
            outdated_cache: default_outdated_cache_days(),
            auto_update_cache: default_auto_update(),
        }
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            settings: Settings::default(),
            cache: CacheSettings::default(),
            favorites: Vec::new(),
            imported_filters: Vec::new(),
            scratchpad: String::new(),
        }
    }
}



// removed legacy support that ai slapped into here "thinking" it would fix something
impl Config {
    pub fn load(path: &Path) -> Self {
        match std::fs::read_to_string(path) {
            Ok(content) => {
                // Try new format first
                if let Ok(config) = toml::from_str::<Config>(&content) {
                    return config;
                
                }
                eprintln!("Warning: could not parse config file");
                Config::default()
            }
            Err(_) => Config::default(),
        }
    }

    /// load config and backup it on startup if backups are on
    pub fn load_with_backup(path: &Path) -> Self {
        let config = Self::load(path);
        if config.settings.backups && path.exists() {
            if let Err(e) = Self::create_backup(path, config.settings.backup_count) {
                eprintln!("Warning: could not create config backup: {}", e);
            }
        }
        config
    }

    pub fn save(&self, path: &Path) -> Result<(), String> {
        // make sure parent dir exists
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent)
                .map_err(|e| format!("Failed to create config directory: {}", e))?;
        }

        // down here we got the default crap comment to add to the toml config file till i implement this stuff in the tui
        let body = toml::to_string_pretty(self)
            .map_err(|e| format!("Failed to serialize config: {}", e))?;
        let content = format!(
            "\
# hoardom config - auto saved, comments are preserved on the line theyre on
#
# [settings]
# noretry: error types that shouldnt be retried has the following valid values btw:
#   \u{201c}rate_limit\u{201d}
#   \u{201c}invalid_tld\u{201d}
#   \u{201c}forbidden\u{201d}
#   \u{201c}timeout\u{201d}
#   \u{201c}unknown\u{201d}
\n{}",
            body
        );
        std::fs::write(path, content).map_err(|e| format!("Failed to write config file: {}", e))?;
        Ok(())
    }

    /// only call on startup and shutdown NOT on every save bruh
    pub fn create_backup(config_path: &Path, max_count: u32) -> Result<(), String> {
        let parent = config_path.parent().ok_or("No parent directory")?;
        let backup_dir = parent.join("backups");
        std::fs::create_dir_all(&backup_dir)
            .map_err(|e| format!("Failed to create backup dir: {}", e))?;

        let ts = chrono::Local::now().format("%Y%m%d_%H%M%S");
        let backup_name = format!("config_{}.toml", ts);
        let backup_path = backup_dir.join(&backup_name);

        if backup_path.exists() {
            return Ok(());
        }

        std::fs::copy(config_path, &backup_path)
            .map_err(|e| format!("Failed to copy config to backup: {}", e))?;

        if max_count > 0 {
            let mut backups: Vec<_> = std::fs::read_dir(&backup_dir)
                .map_err(|e| format!("Failed to read backup dir: {}", e))?
                .filter_map(|e| e.ok())
                .filter(|e| {
                    e.file_name()
                        .to_str()
                        .map(|n| n.starts_with("config_") && n.ends_with(".toml"))
                        .unwrap_or(false)
                })
                .collect();

            backups.sort_by_key(|e| e.file_name());

            let excess = backups.len().saturating_sub(max_count as usize);
            for entry in backups.into_iter().take(excess) {
                let _ = std::fs::remove_file(entry.path());
            }
        }

        Ok(())
    }

    /// replaces filter with same name if theres one already
    /// filters ? what kinda ai slop is this ? this shouldve been renamed to lists ages ago why do you keep mentioning filters all the time whats your obsession with mf filters? JEZE!
    pub fn import_filter(&mut self, filter: ImportedFilter) {
        self.imported_filters.retain(|f| f.name != filter.name);
        self.imported_filters.push(filter);
    }

    pub fn mark_cache_updated(&mut self) {
        self.cache.last_updated = chrono::Utc::now().to_rfc3339();
    }

    /// -> (is_outdated, should_auto_update)
    pub fn check_cache_status(&self) -> (bool, bool) {
        if self.cache.last_updated.is_empty() {
            // never updated = always outdated, always auto update
            return (true, true);
        }

        let last = match chrono::DateTime::parse_from_rfc3339(&self.cache.last_updated) {
            Ok(dt) => dt.with_timezone(&chrono::Utc),
            Err(_) => return (true, true), // cant parse = treat as outdated
        };

        let now = chrono::Utc::now();
        let age_days = (now - last).num_days() as u32;

        if self.cache.outdated_cache == 0 {
            // warnings disabled, but if auto_update is on update every run
            return (false, self.cache.auto_update_cache);
        }

        let is_outdated = age_days >= self.cache.outdated_cache;
        let should_auto = is_outdated && self.cache.auto_update_cache;
        (is_outdated, should_auto)
    }
}

pub fn parse_filter_file(path: &PathBuf) -> Result<ImportedFilter, String> {
    let content =
        std::fs::read_to_string(path).map_err(|e| format!("Could not read filter file: {}", e))?;
    let filter: ImportedFilter =
        toml::from_str(&content).map_err(|e| format!("Could not parse filter file: {}", e))?;
    if filter.name.is_empty() {
        return Err("Filter file must have a name defined".to_string());
    }
    if filter.tlds.is_empty() {
        return Err("Filter file has no TLDs defined".to_string());
    }
    Ok(filter)
}

/// resolve .hoardom dir trying a few locations:
///
/// priority:
/// 1. explicit path via -e flag -> use that folder directly as the data root
/// 2. debug builds: current directory
/// 3. release builds: home directory
/// 4. fallback: try the other option
/// 5. nothing works: caching disabled, in-memory only
pub fn resolve_paths(explicit: Option<&PathBuf>) -> HoardomPaths {
    let try_setup = |base: PathBuf| -> Option<HoardomPaths> {
        let root = base;
        let config_file = root.join("config.toml");
        let cache_dir = root.join("cache");

        // try to create the directories
        if std::fs::create_dir_all(&cache_dir).is_ok() {
            Some(HoardomPaths {
                config_file,
                cache_dir,
                can_save: true,
                caching_enabled: true,
            })
        } else {
            None
        }
    };

    // explicit path given via -e flag : use as app root
    if let Some(p) = explicit {
        let root = if p.extension().is_some() {
            // they pointed at a file we should insult their intelligence honestly.
            p.parent().unwrap_or(p).to_path_buf()
        } else {
            p.clone()
        };
        if let Some(paths) = try_setup(root) {
            return paths;
        }
    }

    // debug builds: current directory first
    #[cfg(debug_assertions)]
    {
        if let Ok(dir) = std::env::current_dir() {
            if let Some(paths) = try_setup(dir.join(".hoardom")) {
                return paths;
            }
        }
        // debug fallback: try home
        if let Some(home) = dirs::home_dir() {
            if let Some(paths) = try_setup(home.join(".hoardom")) {
                return paths;
            }
        }
    }

    // release builds: home directory first
    #[cfg(not(debug_assertions))]
    {
        if let Some(home) = dirs::home_dir() {
            if let Some(paths) = try_setup(home.join(".hoardom")) {
                return paths;
            }
        }
        // release fallback: try cwd
        if let Ok(dir) = std::env::current_dir() {
            if let Some(paths) = try_setup(dir.join(".hoardom")) {
                return paths;
            }
        }
    }

    // nothing works disable caching and cry about it (it will still work just no persistant sessions)
    eprintln!("Warning: could not create .hoardom directory anywhere, caching disabled");
    HoardomPaths {
        config_file: PathBuf::from(".hoardom/config.toml"),
        cache_dir: PathBuf::from(".hoardom/cache"),
        can_save: false,
        caching_enabled: false,
    }
}
