//! Path utilities module for centralized path resolution
//!
//! This module provides utilities for resolving paths relative to the project
//! root, eliminating the need for hardcoded "../.." paths throughout the
//! codebase.

#![allow(dead_code)]

use std::env;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

/// Global project root path, initialized once at startup
static PROJECT_ROOT: OnceLock<PathBuf> = OnceLock::new();

/// Global site name (brand), set once from server config (`Config.app.name`)
/// at startup. Lets functions without access to `Config` (SSR shell, route
/// title builder) render the configured brand instead of a hardcoded literal.
static SITE_NAME: OnceLock<String> = OnceLock::new();

/// Set the site name (brand) from server config at startup. The first set wins.
pub fn set_site_name(name: String) {
    let _ = SITE_NAME.set(name);
}

/// The configured site name (brand), or `"Rustelo"` when unset.
pub fn site_name() -> String {
    SITE_NAME
        .get()
        .cloned()
        .unwrap_or_else(|| "Rustelo".to_string())
}

/// Detectors
pub mod lang_detection;
pub mod theme_detection;

/// HTMX request inspection and response helpers.
pub mod htmx;

/// Initialize the project root path
pub fn init_project_root() -> PathBuf {
    PROJECT_ROOT
        .get_or_init(|| {
            // Try to get root path from environment variable first
            if let Ok(root_path) = env::var("PROJECT_ROOT") {
                return PathBuf::from(root_path);
            }

            // Try to get root path from config if available
            if let Ok(config_root) = env::var("CONFIG_ROOT") {
                return PathBuf::from(config_root);
            }

            // Fall back to detecting based on current executable or working directory
            detect_project_root()
        })
        .clone()
}

/// Detect the project root directory
fn detect_project_root() -> PathBuf {
    // Start with current working directory
    let mut current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));

    // Look for Cargo.toml in current directory and parent directories
    loop {
        let cargo_toml = current_dir.join("Cargo.toml");
        if cargo_toml.exists() {
            // Check if this is a workspace root (has [workspace] section)
            if let Ok(contents) = std::fs::read_to_string(&cargo_toml) {
                if contents.contains("[workspace]") {
                    return current_dir;
                }
            }
        }

        // Move up one directory
        if let Some(parent) = current_dir.parent() {
            current_dir = parent.to_path_buf();
        } else {
            break;
        }
    }

    // If we couldn't find workspace root, use current directory
    env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
}

/// Get the project root path
pub fn get_project_root() -> &'static PathBuf {
    PROJECT_ROOT.get_or_init(detect_project_root)
}

/// Resolve a path relative to the project root
pub fn resolve_from_root<P: AsRef<Path>>(relative_path: P) -> PathBuf {
    get_project_root().join(relative_path)
}

/// Resolve a path relative to the project root and return as string
#[allow(dead_code)]
pub fn resolve_from_root_str<P: AsRef<Path>>(relative_path: P) -> String {
    resolve_from_root(relative_path)
        .to_string_lossy()
        .to_string()
}

/// Get the absolute path for a given relative path from project root
#[allow(dead_code)]
pub fn get_absolute_path<P: AsRef<Path>>(relative_path: P) -> Result<PathBuf, std::io::Error> {
    let resolved = resolve_from_root(relative_path);
    resolved.canonicalize()
}

/// Check if a path exists relative to project root
#[allow(dead_code)]
pub fn exists_from_root<P: AsRef<Path>>(relative_path: P) -> bool {
    resolve_from_root(relative_path).exists()
}

/// Read a file relative to project root
#[allow(dead_code)]
pub fn read_file_from_root<P: AsRef<Path>>(relative_path: P) -> Result<String, std::io::Error> {
    let path = resolve_from_root(relative_path);
    std::fs::read_to_string(path)
}

/// Read a file relative to project root, with fallback to embedded content
#[allow(dead_code)]
pub fn read_file_from_root_or_embedded<P: AsRef<Path>>(
    relative_path: P,
    embedded_content: &str,
) -> String {
    read_file_from_root(relative_path).unwrap_or_else(|_| embedded_content.to_string())
}

/// Path constants for commonly used directories
pub mod paths {
    use super::*;

    /// Overridable migrations path — set once by the application before any DB
    /// init.
    static MIGRATIONS_DIR: OnceLock<PathBuf> = OnceLock::new();
    /// Overridable email templates path — set once by the application before
    /// email init.
    static EMAIL_TEMPLATES_DIR: OnceLock<PathBuf> = OnceLock::new();

    /// Configure the migrations directory from a project-root-relative path.
    ///
    /// Must be called before the first [`migrations_dir`] call (i.e. before
    /// database initialization).  Subsequent calls are silently ignored
    /// (OnceLock semantics).
    pub fn set_migrations_dir(relative: &str) {
        let _ = MIGRATIONS_DIR.set(get_project_root().join(relative));
    }

    /// Configure the email templates directory from a project-root-relative
    /// path.
    ///
    /// Must be called before [`email_templates_dir`] is first used.
    pub fn set_email_templates_dir(relative: &str) {
        let _ = EMAIL_TEMPLATES_DIR.set(get_project_root().join(relative));
    }

    /// Get the config directory path
    #[allow(dead_code)]
    pub fn config_dir() -> PathBuf {
        resolve_from_root("config")
    }

    /// Get the content directory path
    #[allow(dead_code)]
    pub fn content_dir() -> PathBuf {
        resolve_from_root("content")
    }

    /// Get the migrations directory path.
    ///
    /// Returns the value set via [`set_migrations_dir`], or
    /// `{project_root}/migrations` if the override was never configured.
    pub fn migrations_dir() -> PathBuf {
        MIGRATIONS_DIR
            .get_or_init(|| resolve_from_root("migrations"))
            .clone()
    }

    /// Get the email templates directory path.
    ///
    /// Returns the value set via [`set_email_templates_dir`], or
    /// `{project_root}/email_templates` if the override was never configured.
    pub fn email_templates_dir() -> PathBuf {
        EMAIL_TEMPLATES_DIR
            .get_or_init(|| resolve_from_root("email_templates"))
            .clone()
    }

    /// Get the public directory path
    #[allow(dead_code)]
    pub fn public_dir() -> PathBuf {
        resolve_from_root("public")
    }

    /// Get the uploads directory path
    #[allow(dead_code)]
    pub fn uploads_dir() -> PathBuf {
        resolve_from_root("uploads")
    }

    /// Get the logs directory path
    #[allow(dead_code)]
    pub fn logs_dir() -> PathBuf {
        resolve_from_root("logs")
    }

    /// Get the certs directory path
    #[allow(dead_code)]
    pub fn certs_dir() -> PathBuf {
        resolve_from_root("certs")
    }

    /// Get the cache directory path
    #[allow(dead_code)]
    pub fn cache_dir() -> PathBuf {
        resolve_from_root("cache")
    }

    /// Get the data directory path
    #[allow(dead_code)]
    pub fn data_dir() -> PathBuf {
        resolve_from_root("data")
    }

    /// Get the backup directory path
    #[allow(dead_code)]
    pub fn backup_dir() -> PathBuf {
        resolve_from_root("backups")
    }
}

/// Configuration file utilities
pub mod config {
    use super::*;

    /// Get the path to a configuration file
    #[allow(dead_code)]
    pub fn config_file<P: AsRef<Path>>(filename: P) -> PathBuf {
        resolve_from_root(filename)
    }

    /// Get the path to the main config file
    #[allow(dead_code)]
    pub fn main_config() -> PathBuf {
        config_file("config.toml")
    }

    /// Get the path to the development config file
    #[allow(dead_code)]
    pub fn dev_config() -> PathBuf {
        config_file("config.dev.toml")
    }

    /// Get the path to the production config file
    #[allow(dead_code)]
    pub fn prod_config() -> PathBuf {
        config_file("config.prod.toml")
    }

    /// Read a config file with fallback to embedded content
    #[allow(dead_code)]
    pub fn read_config_or_embedded(filename: &str, embedded: &str) -> String {
        read_file_from_root_or_embedded(filename, embedded)
    }
}

/// Content file utilities
pub mod content {
    use super::*;

    /// Get the path to a content file
    #[allow(dead_code)]
    pub fn content_file<P: AsRef<Path>>(filename: P) -> PathBuf {
        resolve_from_root("content").join(filename)
    }

    /// Read a content file with fallback to embedded content
    #[allow(dead_code)]
    pub fn read_content_or_embedded(filename: &str, embedded: &str) -> String {
        let path = content_file(filename);
        std::fs::read_to_string(path).unwrap_or_else(|_| embedded.to_string())
    }
}

/// Migration file utilities
pub mod migrations {
    use super::*;

    /// Get the path to a migration file
    #[allow(dead_code)]
    pub fn migration_file<P: AsRef<Path>>(filename: P) -> PathBuf {
        resolve_from_root("migrations").join(filename)
    }

    /// Read a migration file
    #[allow(dead_code)]
    pub fn read_migration_file(filename: &str) -> Result<String, std::io::Error> {
        let path = migration_file(filename);
        std::fs::read_to_string(path)
    }
}

/// Macro for getting paths from project root
#[macro_export]
macro_rules! project_file {
    ($relative_path:expr) => {
        $crate::utils::read_file_from_root($relative_path)
    };
}

/// Macro for getting absolute paths from project root
#[macro_export]
macro_rules! project_path {
    ($relative_path:expr) => {
        $crate::utils::resolve_from_root($relative_path)
    };
}

/// Initialize the path utilities system
pub fn init() {
    init_project_root();
}

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

    #[test]
    fn test_project_root_detection() {
        let root = get_project_root();
        assert!(root.is_absolute());

        // Should find the workspace Cargo.toml
        let cargo_toml = root.join("Cargo.toml");
        assert!(cargo_toml.exists());
    }

    #[test]
    fn test_path_resolution() {
        let config_path = resolve_from_root("config.toml");
        assert!(config_path.is_absolute());
        assert!(config_path.to_string_lossy().ends_with("config.toml"));
    }

    #[test]
    fn test_path_constants() {
        let config_dir = paths::config_dir();
        assert!(config_dir.is_absolute());
        assert!(config_dir.to_string_lossy().ends_with("config"));

        let content_dir = paths::content_dir();
        assert!(content_dir.is_absolute());
        assert!(content_dir.to_string_lossy().ends_with("content"));
    }

    #[test]
    fn test_config_utilities() {
        let main_config = config::main_config();
        assert!(main_config.is_absolute());
        assert!(main_config.to_string_lossy().ends_with("config.toml"));

        let dev_config = config::dev_config();
        assert!(dev_config.is_absolute());
        assert!(dev_config.to_string_lossy().ends_with("config.dev.toml"));
    }

    #[test]
    fn test_exists_from_root() {
        // Test with a file that should exist
        assert!(exists_from_root("Cargo.toml"));

        // Test with a file that shouldn't exist
        assert!(!exists_from_root("non_existent_file.txt"));
    }
}
