use crate::observability::config::{LogFilterConfig, LogFilterOverrideConfig};
use crate::observability::filter::SharedOrderedFilter;
use anyhow::{bail, Result};

/// Runtime reload operations for one observability filter scope.
#[derive(Clone, Debug)]
pub struct FilterReloadHandle {
    enabled: bool,
    scope: &'static str,
    filter: SharedOrderedFilter,
}

impl FilterReloadHandle {
    pub(crate) fn new(enabled: bool, scope: &'static str, filter: SharedOrderedFilter) -> Self {
        Self {
            enabled,
            scope,
            filter,
        }
    }

    /// Replaces this scope's default level.
    ///
    /// Invalid levels return an error and leave the active filter unchanged.
    pub fn reload_default_level(&self, level: &str) -> Result<()> {
        self.ensure_enabled()?;
        self.filter.reload_default_level(level)
    }

    /// Replaces this scope's override rules.
    ///
    /// Invalid levels, fuzzy regex/glob rules, or field regex rules return an
    /// error and leave the active filter unchanged.
    pub fn reload_overrides(&self, rules: Vec<LogFilterOverrideConfig>) -> Result<()> {
        self.ensure_enabled()?;
        self.filter.reload_overrides(rules)
    }

    /// Enables or disables one override rule by name in this scope.
    ///
    /// The rule name must exist and names must be unique in the active filter
    /// configuration. Invalid updated configurations leave the active filter
    /// unchanged.
    pub fn set_override_enabled(&self, name: &str, enabled: bool) -> Result<()> {
        self.ensure_enabled()?;
        self.filter.set_override_enabled(name, enabled)
    }

    /// Replaces this scope's full filter configuration.
    pub fn reload_filter(&self, config: LogFilterConfig) -> Result<()> {
        self.ensure_enabled()?;
        self.filter.reload_filter(config)
    }

    pub(crate) fn reload_filter_unchecked(&self, config: LogFilterConfig) -> Result<()> {
        self.filter.reload_filter(config)
    }

    /// Returns this scope's currently effective filter configuration.
    pub fn current_filter_config(&self) -> LogFilterConfig {
        self.filter.current_config()
    }

    fn ensure_enabled(&self) -> Result<()> {
        if !self.enabled {
            bail!(
                "dynamic observability reload is disabled for `{}`",
                self.scope
            );
        }
        Ok(())
    }
}

/// Runtime reload handle for local logs, remote logs, and traces.
#[derive(Clone, Debug)]
pub struct ObservabilityReloadHandle {
    local: FilterReloadHandle,
    remote: Option<FilterReloadHandle>,
    trace: Option<FilterReloadHandle>,
    remote_uses_local_filter: bool,
}

impl ObservabilityReloadHandle {
    pub(crate) fn new(
        local: FilterReloadHandle,
        remote: Option<FilterReloadHandle>,
        trace: Option<FilterReloadHandle>,
        remote_uses_local_filter: bool,
    ) -> Self {
        Self {
            local,
            remote,
            trace,
            remote_uses_local_filter,
        }
    }

    /// Returns the console/local-file log filter reload scope.
    pub fn local(&self) -> &FilterReloadHandle {
        &self.local
    }

    /// Returns the remote OpenTelemetry log filter reload scope.
    ///
    /// This returns an error when remote logs were disabled during
    /// initialization.
    pub fn remote(&self) -> Result<&FilterReloadHandle> {
        self.remote
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("remote log filter is not initialized"))
    }

    /// Returns the OpenTelemetry trace filter reload scope.
    ///
    /// This returns an error when trace export was disabled during
    /// initialization.
    pub fn trace(&self) -> Result<&FilterReloadHandle> {
        self.trace
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("trace filter is not initialized"))
    }

    pub(crate) fn remote_uses_local_filter(&self) -> bool {
        self.remote_uses_local_filter
    }

    /// Replaces the local default log level.
    ///
    /// This legacy method is equivalent to
    /// `reload.local().reload_default_level(level)`.
    pub fn reload_default_level(&self, level: &str) -> Result<()> {
        self.local.reload_default_level(level)
    }

    /// Replaces local override rules.
    ///
    /// This legacy method is equivalent to
    /// `reload.local().reload_overrides(rules)`.
    pub fn reload_overrides(&self, rules: Vec<LogFilterOverrideConfig>) -> Result<()> {
        self.local.reload_overrides(rules)
    }

    /// Enables or disables one local override rule by name.
    ///
    /// This legacy method is equivalent to
    /// `reload.local().set_override_enabled(name, enabled)`.
    pub fn set_override_enabled(&self, name: &str, enabled: bool) -> Result<()> {
        self.local.set_override_enabled(name, enabled)
    }

    /// Replaces the full local filter configuration.
    ///
    /// This legacy method is equivalent to `reload.local().reload_filter(config)`.
    pub fn reload_filter(&self, config: LogFilterConfig) -> Result<()> {
        self.local.reload_filter(config)
    }

    /// Returns the currently effective local filter configuration.
    ///
    /// This legacy method is equivalent to
    /// `reload.local().current_filter_config()`.
    pub fn current_filter_config(&self) -> LogFilterConfig {
        self.local.current_filter_config()
    }
}

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

    #[test]
    fn scoped_reload_updates_only_selected_filter() {
        let local = reload_scope("local", "info");
        let remote = reload_scope("remote", "off");
        let trace = reload_scope("trace", "warn");
        let reload = ObservabilityReloadHandle::new(local, Some(remote), Some(trace), false);

        reload
            .remote()
            .unwrap()
            .reload_default_level("debug")
            .unwrap();
        reload
            .trace()
            .unwrap()
            .reload_default_level("trace")
            .unwrap();

        assert_eq!(reload.local().current_filter_config().default_level, "info");
        assert_eq!(
            reload
                .remote()
                .unwrap()
                .current_filter_config()
                .default_level,
            "debug"
        );
        assert_eq!(
            reload
                .trace()
                .unwrap()
                .current_filter_config()
                .default_level,
            "trace"
        );
    }

    #[test]
    fn missing_remote_and_trace_scopes_return_errors() {
        let reload =
            ObservabilityReloadHandle::new(reload_scope("local", "info"), None, None, false);

        assert!(reload
            .remote()
            .unwrap_err()
            .to_string()
            .contains("remote log filter is not initialized"));
        assert!(reload
            .trace()
            .unwrap_err()
            .to_string()
            .contains("trace filter is not initialized"));
    }

    #[test]
    fn disabled_reload_rejects_scope_updates() {
        let filter = SharedOrderedFilter::new(filter_config("info")).unwrap();
        let scope = FilterReloadHandle::new(false, "trace", filter);

        let error = scope.reload_default_level("debug").unwrap_err();
        assert!(error
            .to_string()
            .contains("dynamic observability reload is disabled for `trace`"));
    }

    fn reload_scope(scope: &'static str, level: &str) -> FilterReloadHandle {
        let filter = SharedOrderedFilter::new(filter_config(level)).unwrap();
        FilterReloadHandle::new(true, scope, filter)
    }

    fn filter_config(level: &str) -> LogFilterConfig {
        LogFilterConfig {
            default_level: level.to_string(),
            overrides: Vec::new(),
        }
    }
}