//! Filters applied on the subscriber side of the SharingBus.

use std::ops::{BitOr, BitOrAssign};

use uuid::Uuid;

use super::bus_event::{BusEvent, EventKind};

/// A subscriber-side predicate that selects which `BusEvent`s to forward.
#[derive(Debug, Clone)]
pub enum EventFilter {
    /// Accept every event unconditionally.
    All,
    /// Accept only events whose `routing.scroll_id` matches the given UUID.
    ScrollId(Uuid),
    /// Accept only events whose `routing.connector_uid` matches the given UUID.
    ConnectorUid(Uuid),
    /// Accept only events whose `routing.kind` is set in the mask.
    Kinds(EventKindMask),
    /// Accept events that satisfy at least one of the inner filters.
    AnyOf(Vec<EventFilter>),
    /// Accept events that satisfy all of the inner filters.
    AllOf(Vec<EventFilter>),
}

/// Bit-mask over `EventKind` variants for efficient kind-based filtering.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct EventKindMask(pub u8);

impl EventKindMask {
    pub const SESSION_LIFECYCLE: Self = Self(1 << 0);
    pub const MESSAGE: Self = Self(1 << 1);
    pub const UPDATE: Self = Self(1 << 2);
    pub const SYSTEM: Self = Self(1 << 3);
    pub const ALL: Self = Self(0b1111);

    /// Returns `true` if `kind` is set in this mask.
    pub fn contains(self, kind: EventKind) -> bool {
        let bit = match kind {
            EventKind::SessionLifecycle => Self::SESSION_LIFECYCLE,
            EventKind::Message => Self::MESSAGE,
            EventKind::Update => Self::UPDATE,
            EventKind::System => Self::SYSTEM,
        };
        (self.0 & bit.0) != 0
    }
}

impl BitOr for EventKindMask {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self::Output {
        Self(self.0 | rhs.0)
    }
}

impl BitOrAssign for EventKindMask {
    fn bitor_assign(&mut self, rhs: Self) {
        self.0 |= rhs.0;
    }
}

impl EventFilter {
    /// Returns `true` if this filter accepts the given `BusEvent`.
    pub fn matches(&self, event: &BusEvent) -> bool {
        match self {
            EventFilter::All => true,
            EventFilter::ScrollId(s) => event.routing.scroll_id == Some(*s),
            EventFilter::ConnectorUid(u) => event.routing.connector_uid == Some(*u),
            EventFilter::Kinds(m) => m.contains(event.routing.kind),
            EventFilter::AnyOf(filters) => filters.iter().any(|f| f.matches(event)),
            EventFilter::AllOf(filters) => filters.iter().all(|f| f.matches(event)),
        }
    }
}

// ─── Tests ───────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use uuid::Uuid;

    use super::*;
    use crate::{
        streaming::bus_event::{EventOrigin, EventRouting},
        Event,
    };

    /// Build a minimal `BusEvent` for testing.
    fn make_event(
        scroll_id: Option<Uuid>,
        connector_uid: Option<Uuid>,
        kind: EventKind,
    ) -> BusEvent {
        BusEvent {
            routing: EventRouting {
                scroll_id,
                connector_uid,
                kind,
                ..Default::default()
            },
            origin: EventOrigin::Runtime,
            event: Arc::new(Event::Connected),
        }
    }

    // 1. EventFilter::All matches any BusEvent.
    #[test]
    fn all_matches_any_event() {
        let ev = make_event(None, None, EventKind::System);
        assert!(EventFilter::All.matches(&ev));

        let ev2 = make_event(Some(Uuid::new_v4()), Some(Uuid::new_v4()), EventKind::Message);
        assert!(EventFilter::All.matches(&ev2));
    }

    // 2. EventFilter::ScrollId matches Some(x), rejects Some(y), rejects None.
    #[test]
    fn scroll_id_matches_correct_uuid_only() {
        let x = Uuid::new_v4();
        let y = Uuid::new_v4();

        let filter = EventFilter::ScrollId(x);

        let ev_match = make_event(Some(x), None, EventKind::SessionLifecycle);
        assert!(filter.matches(&ev_match), "should match Some(x)");

        let ev_other = make_event(Some(y), None, EventKind::SessionLifecycle);
        assert!(!filter.matches(&ev_other), "should reject Some(y)");

        let ev_none = make_event(None, None, EventKind::SessionLifecycle);
        assert!(!filter.matches(&ev_none), "should reject None");
    }

    // 3. EventFilter::ConnectorUid matches only when routing.connector_uid == Some(u).
    #[test]
    fn connector_uid_matches_correct_uuid_only() {
        let u = Uuid::new_v4();
        let other = Uuid::new_v4();

        let filter = EventFilter::ConnectorUid(u);

        let ev_match = make_event(None, Some(u), EventKind::Update);
        assert!(filter.matches(&ev_match));

        let ev_other = make_event(None, Some(other), EventKind::Update);
        assert!(!filter.matches(&ev_other));

        let ev_none = make_event(None, None, EventKind::Update);
        assert!(!filter.matches(&ev_none));
    }

    // 4. EventFilter::Kinds(MESSAGE) matches Message, rejects Update.
    #[test]
    fn kinds_mask_message_matches_message_only() {
        let filter = EventFilter::Kinds(EventKindMask::MESSAGE);

        let ev_msg = make_event(None, None, EventKind::Message);
        assert!(filter.matches(&ev_msg));

        let ev_upd = make_event(None, None, EventKind::Update);
        assert!(!filter.matches(&ev_upd));
    }

    // 5. AnyOf([ScrollId(X), ConnectorUid(Y)]) matches when either matches, rejects otherwise.
    #[test]
    fn any_of_matches_when_at_least_one_sub_filter_matches() {
        let x = Uuid::new_v4();
        let y = Uuid::new_v4();
        let z = Uuid::new_v4();

        let filter = EventFilter::AnyOf(vec![
            EventFilter::ScrollId(x),
            EventFilter::ConnectorUid(y),
        ]);

        // scroll_id matches
        let ev_scroll = make_event(Some(x), None, EventKind::Message);
        assert!(filter.matches(&ev_scroll));

        // connector_uid matches
        let ev_conn = make_event(None, Some(y), EventKind::Message);
        assert!(filter.matches(&ev_conn));

        // both match
        let ev_both = make_event(Some(x), Some(y), EventKind::Message);
        assert!(filter.matches(&ev_both));

        // neither matches
        let ev_neither = make_event(Some(z), Some(z), EventKind::System);
        assert!(!filter.matches(&ev_neither));
    }

    // 6. AllOf([ScrollId(X), Kinds(MESSAGE)]) matches only when both hold.
    #[test]
    fn all_of_matches_only_when_all_sub_filters_match() {
        let x = Uuid::new_v4();

        let filter = EventFilter::AllOf(vec![
            EventFilter::ScrollId(x),
            EventFilter::Kinds(EventKindMask::MESSAGE),
        ]);

        // both conditions satisfied
        let ev_both = make_event(Some(x), None, EventKind::Message);
        assert!(filter.matches(&ev_both));

        // scroll_id matches but wrong kind
        let ev_wrong_kind = make_event(Some(x), None, EventKind::Update);
        assert!(!filter.matches(&ev_wrong_kind));

        // right kind but wrong scroll_id
        let ev_wrong_scroll = make_event(Some(Uuid::new_v4()), None, EventKind::Message);
        assert!(!filter.matches(&ev_wrong_scroll));

        // neither matches
        let ev_neither = make_event(None, None, EventKind::System);
        assert!(!filter.matches(&ev_neither));
    }

    // 7. BitOr combining masks: (MESSAGE | UPDATE).contains(kind) is true for both.
    #[test]
    fn bitor_combines_masks_correctly() {
        let combined = EventKindMask::MESSAGE | EventKindMask::UPDATE;

        assert!(combined.contains(EventKind::Message));
        assert!(combined.contains(EventKind::Update));
        assert!(!combined.contains(EventKind::SessionLifecycle));
        assert!(!combined.contains(EventKind::System));
    }

    // Bonus: verify BitOrAssign works the same way.
    #[test]
    fn bitor_assign_accumulates_bits() {
        let mut mask = EventKindMask::MESSAGE;
        mask |= EventKindMask::SYSTEM;

        assert!(mask.contains(EventKind::Message));
        assert!(mask.contains(EventKind::System));
        assert!(!mask.contains(EventKind::Update));
    }
}
