use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};

use tracing_subscriber::Registry;
use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::reload;

static LOG_FILTER_HANDLE: OnceLock<reload::Handle<EnvFilter, Registry>> = OnceLock::new();
static LOG_DIRECTIVES: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();

/// Initializes runtime logging with reloadable filter directives.
///
/// Safe to call multiple times; only the first successful call installs the
/// global tracing subscriber.
pub fn init_runtime_logging(default_level: &str) -> Result<(), String> {
    if LOG_FILTER_HANDLE.get().is_some() {
        return Ok(());
    }

    let normalized_default = normalize_level(default_level)?;
    let directives = LOG_DIRECTIVES.get_or_init(|| Mutex::new(HashMap::new()));
    {
        let mut guard = directives
            .lock()
            .map_err(|_| "log directives lock poisoned".to_string())?;
        if guard.is_empty() {
            guard.insert("all".to_string(), normalized_default.to_string());
        }
    }

    let filter = {
        let guard = directives
            .lock()
            .map_err(|_| "log directives lock poisoned".to_string())?;
        build_env_filter(&guard)?
    };

    let (reload_layer, handle) = reload::Layer::new(filter);
    let fmt_layer = if use_json_log_format() {
        tracing_subscriber::fmt::layer().json().boxed()
    } else {
        tracing_subscriber::fmt::layer().boxed()
    };
    let subscriber = tracing_subscriber::registry()
        .with(reload_layer)
        .with(fmt_layer);
    tracing::subscriber::set_global_default(subscriber)
        .map_err(|err| format!("set global tracing subscriber failed: {err}"))?;
    let _ = LOG_FILTER_HANDLE.set(handle);
    Ok(())
}

/// Updates runtime log filtering for `target` to the provided level.
///
/// `target = \"all\"` updates the global default level.
pub fn set_runtime_log_level(target: &str, level: &str) -> Result<(), String> {
    let handle = LOG_FILTER_HANDLE
        .get()
        .ok_or_else(|| "runtime logging is not initialized".to_string())?;
    let normalized_level = normalize_level(level)?;
    let normalized_target = normalize_target(target)?;

    let filter = {
        let directives = LOG_DIRECTIVES.get_or_init(|| Mutex::new(HashMap::new()));
        let mut guard = directives
            .lock()
            .map_err(|_| "log directives lock poisoned".to_string())?;
        guard.insert(normalized_target, normalized_level.to_string());
        build_env_filter(&guard)?
    };

    handle
        .reload(filter)
        .map_err(|err| format!("reload tracing filter failed: {err}"))?;
    Ok(())
}

fn normalize_level(level: &str) -> Result<&'static str, String> {
    match level.trim().to_ascii_lowercase().as_str() {
        "trace" => Ok("trace"),
        "debug" => Ok("debug"),
        "info" => Ok("info"),
        "warn" => Ok("warn"),
        "error" => Ok("error"),
        other => Err(format!(
            "unsupported log level '{other}', expected one of trace|debug|info|warn|error"
        )),
    }
}

fn normalize_target(target: &str) -> Result<String, String> {
    let target = target.trim();
    if target.is_empty() || target.eq_ignore_ascii_case("all") {
        return Ok("all".to_string());
    }
    if target.contains(',') {
        return Err("target must not contain ','".to_string());
    }
    Ok(target.to_string())
}

fn build_env_filter(directives: &HashMap<String, String>) -> Result<EnvFilter, String> {
    let mut spec = String::new();
    let default_level = directives
        .get("all")
        .map(String::as_str)
        .unwrap_or("info")
        .to_string();
    spec.push_str(&default_level);

    let mut targets = directives
        .iter()
        .filter_map(|(target, level)| {
            if target == "all" {
                None
            } else {
                Some((target, level))
            }
        })
        .collect::<Vec<_>>();
    targets.sort_by_key(|(target, _)| *target);
    for (target, level) in targets {
        spec.push(',');
        spec.push_str(target);
        spec.push('=');
        spec.push_str(level);
    }

    EnvFilter::try_new(spec).map_err(|err| format!("invalid log filter directives: {err}"))
}

fn use_json_log_format() -> bool {
    std::env::var("PD_TSO_LOG_FORMAT")
        .ok()
        .map(|raw| raw.trim().to_ascii_lowercase())
        .map(|raw| !matches!(raw.as_str(), "plain" | "text"))
        .unwrap_or(true)
}

#[cfg(test)]
mod tests {
    use super::{normalize_level, use_json_log_format};
    use pd_test_util::env::{EnvVarRestore, async_env_lock, remove_env, set_env};

    #[test]
    fn supports_known_log_levels() {
        assert_eq!(normalize_level("trace").expect("trace"), "trace");
        assert_eq!(normalize_level("DEBUG").expect("debug"), "debug");
        assert_eq!(normalize_level("Info").expect("info"), "info");
        assert_eq!(normalize_level("warn").expect("warn"), "warn");
        assert_eq!(normalize_level("error").expect("error"), "error");
    }

    #[test]
    fn rejects_unknown_log_levels() {
        assert!(normalize_level("fatal").is_err());
    }

    #[test]
    fn defaults_to_json_format_and_allows_plain_override() {
        let _guard = async_env_lock().blocking_lock();
        let _restore = EnvVarRestore::capture("PD_TSO_LOG_FORMAT");
        remove_env("PD_TSO_LOG_FORMAT");
        assert!(use_json_log_format());
        set_env("PD_TSO_LOG_FORMAT", "plain");
        assert!(!use_json_log_format());
    }
}
