use crate::observability::config::{LogFilterConfig, LogFilterOverrideConfig};
use crate::observability::filter::SharedOrderedFilter;
use anyhow::{bail, Result};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

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

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

    /// 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)?;
        self.bump_revision();
        Ok(())
    }

    /// 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)?;
        self.bump_revision();
        Ok(())
    }

    /// 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)?;
        self.bump_revision();
        Ok(())
    }

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

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

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

    /// Returns whether a JavaScript-owned trace span should be recorded by this
    /// filter scope.
    ///
    /// This is used by FFI bridges that create OpenTelemetry spans directly
    /// from JavaScript and need the same target/span level policy as the
    /// subscriber trace filter.
    pub fn enabled_for_target_span(&self, level: &str, target: &str, span: &str) -> Result<bool> {
        self.filter.enabled_for_target_span(level, target, span)
    }

    fn bump_revision(&self) {
        self.revision.fetch_add(1, Ordering::AcqRel);
    }

    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,
    local_output_enabled: bool,
    revision: Arc<AtomicU64>,
}

impl ObservabilityReloadHandle {
    pub(crate) fn new(
        mut local: FilterReloadHandle,
        mut remote: Option<FilterReloadHandle>,
        mut trace: Option<FilterReloadHandle>,
        remote_uses_local_filter: bool,
        local_output_enabled: bool,
    ) -> Self {
        let revision = Arc::new(AtomicU64::new(1));
        local.revision = revision.clone();
        if let Some(remote) = &mut remote {
            remote.revision = revision.clone();
        }
        if let Some(trace) = &mut trace {
            trace.revision = revision.clone();
        }
        Self {
            local,
            remote,
            trace,
            remote_uses_local_filter,
            local_output_enabled,
            revision,
        }
    }

    /// 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
    }

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

    /// Returns the process-local revision of the currently effective filters.
    ///
    /// The value changes after every successful local, remote, or trace filter
    /// reload. JavaScript integrations can poll this inexpensive value and
    /// fetch a new exported log-filter snapshot only when it changes.
    pub fn current_filter_revision(&self) -> u64 {
        self.revision.load(Ordering::Acquire)
    }

    /// 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, true);

        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, true);

        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(),
        }
    }
}
