//! Bus event envelope: wraps `Event` with routing context for subscribers.

use std::sync::Arc;

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::Event;

/// Full bus event envelope: the `Event` plus routing context derived at emit time.
#[derive(Debug, Clone)]
pub struct BusEvent {
    pub routing: EventRouting,
    pub origin: EventOrigin,
    pub event: Arc<Event>,
}

/// Routing metadata attached to every `BusEvent`.
///
/// `scroll_id` is intentionally left `None` at construction time and filled in
/// later by the bus cache once the archivist has registered the session.
#[derive(Debug, Clone, Default)]
pub struct EventRouting {
    pub connector_uid: Option<Uuid>,
    pub scroll_id: Option<Uuid>,
    pub connector_id: Option<String>,
    pub native_session_id: Option<String>,
    pub kind: EventKind,
}

/// High-level classification of a `BusEvent`, used for subscriber filtering.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventKind {
    #[default]
    SessionLifecycle,
    Message,
    Update,
    System,
}

/// Records the subsystem that originally produced the event.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventOrigin {
    Connector {
        connector_uid: Option<Uuid>,
        connector_id: String,
    },
    Archivist,
    Runtime,
    Replay {
        replay_id: Uuid,
    },
}

// ─── BusEvent constructors ───────────────────────────────────────────────────

impl BusEvent {
    /// Wrap a connector-sourced `Event` in a `BusEvent`.
    ///
    /// Routing metadata is derived from the event fields; `scroll_id` is left
    /// `None` and must be patched by the bus cache after archivist registration.
    pub fn from_connector_event(
        event: Event,
        connector_uid: Option<Uuid>,
        connector_id: String,
    ) -> Self {
        let routing = EventRouting::derive(&event, connector_uid, &connector_id);
        Self {
            routing,
            origin: EventOrigin::Connector {
                connector_uid,
                connector_id,
            },
            event: Arc::new(event),
        }
    }

    /// Wrap an archivist-sourced `Event` (e.g. `SessionRegistered`) in a
    /// `BusEvent`. The archivist knows the canonical `(connector_id,
    /// native_session_id, scroll_id)` triple; we pass it directly so the
    /// bus does not have to consult its scroll-id cache to route the
    /// event. Origin is set to `EventOrigin::Archivist`.
    pub fn from_archivist_event(
        event: Event,
        connector_id: &str,
        native_session_id: &str,
        scroll_id: Option<Uuid>,
    ) -> Self {
        let (kind, _) = classify(&event);
        let routing = EventRouting {
            connector_uid: None,
            scroll_id,
            connector_id: Some(connector_id.to_string()),
            native_session_id: Some(native_session_id.to_string()),
            kind,
        };
        Self {
            routing,
            origin: EventOrigin::Archivist,
            event: Arc::new(event),
        }
    }
}

// ─── EventRouting::derive ────────────────────────────────────────────────────

impl EventRouting {
    /// Derive routing from an `Event` plus the emitting connector's identity.
    ///
    /// `scroll_id` is always `None` here; the bus cache fills it in later.
    pub fn derive(event: &Event, connector_uid: Option<Uuid>, connector_id: &str) -> Self {
        let (kind, native_session_id) = classify(event);
        Self {
            connector_uid,
            scroll_id: None,
            connector_id: Some(connector_id.to_string()),
            native_session_id,
            kind,
        }
    }
}

/// Return the `(EventKind, Option<native_session_id>)` for a given `Event`.
///
/// Every current variant is matched explicitly; the `_` arm is a safety net for
/// future additions and classifies them as `System` with no session context.
fn classify(event: &Event) -> (EventKind, Option<String>) {
    use EventKind::*;

    match event {
        // ── SessionLifecycle ─────────────────────────────────────────────────
        Event::SessionsListed { .. } => (SessionLifecycle, None),

        Event::SessionCreated { session, .. } => {
            (SessionLifecycle, Some(session.id.clone()))
        }

        Event::SessionUpdated { session, .. } => {
            (SessionLifecycle, Some(session.id.clone()))
        }

        Event::SessionMetadataUpdated { session_id, .. } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        Event::SessionDeleted { session_id } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        Event::SessionClosed { session_id, .. } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        Event::SessionSystemMessageSet { session_id, .. } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        Event::SessionIdle { session_id, .. } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        Event::SessionMetadataReceived { session_id, .. } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        Event::SessionTransferred { from_session, .. } => {
            (SessionLifecycle, Some(from_session.clone()))
        }

        Event::SessionRegistered { session_id, .. } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        Event::ForwardingPanic { session_id, .. } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        Event::Connected => (SessionLifecycle, None),
        Event::Disconnected => (SessionLifecycle, None),

        Event::ConnectorCreated { .. } => (SessionLifecycle, None),
        Event::ConnectorRemoved { .. } => (SessionLifecycle, None),
        Event::ConnectorStateChanged { .. } => (SessionLifecycle, None),

        Event::AcpClientConnected { .. } => (SessionLifecycle, None),
        Event::AcpClientDisconnected { .. } => (SessionLifecycle, None),

        Event::AcpClientSessionOpened {
            client_session_id, ..
        } => (SessionLifecycle, Some(client_session_id.clone())),

        Event::AcpClientSessionRouted { from_session_id, .. } => {
            (SessionLifecycle, Some(from_session_id.clone()))
        }

        Event::AgentRequest { session_id, .. } => {
            (SessionLifecycle, Some(session_id.clone()))
        }

        // ── Message ──────────────────────────────────────────────────────────
        Event::MessagesListed { .. } => (Message, None),

        Event::MessageStarted { message, .. } => {
            (Message, Some(message.session_id.clone()))
        }

        Event::MessageCompleted { message, .. } => {
            (Message, Some(message.session_id.clone()))
        }

        Event::MessageFailed { .. } => (Message, None),

        Event::TurnComplete { session_id, .. } => (Message, Some(session_id.clone())),

        // ── Update ───────────────────────────────────────────────────────────
        Event::SessionUpdate { session_id, .. } => (Update, Some(session_id.clone())),

        // ── System ───────────────────────────────────────────────────────────
        Event::Error { .. } => (System, None),
        Event::SessionError { session_id, .. } => (System, Some(session_id.clone())),
        Event::InspectorSnapshot { .. } => (System, None),
        Event::InspectorNodeRegistered { .. } => (System, None),
        Event::InspectorNodeRemoved { .. } => (System, None),
        Event::InspectorStateChanged { .. } => (System, None),
        Event::InspectorPropertiesUpdated { .. } => (System, None),
        Event::SystemTaskStatusChanged { .. } => (System, None),

        // Safety net: future variants not yet listed above.
        // #[allow] is intentional — this arm exists to catch additions to Event.
        #[allow(unreachable_patterns)]
        _ => (System, None),
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Event, Session, SessionMetadata};
    use chrono::Utc;

    fn minimal_session(id: &str) -> Session {
        let now = Utc::now();
        Session {
            id: id.to_string(),
            title: "test".to_string(),
            created_at: now,
            updated_at: now,
            metadata: SessionMetadata {
                project_path: "/tmp".to_string(),
                model: None,
                total_messages: 0,
                system_message: None,
                current_mode_id: None,
                _meta: None,
                project_id: None,
            },
            cwd: None,
            models: None,
            modes: None,
            config_options: None,
            acp_client_id: None,
        }
    }

    #[test]
    fn derive_extracts_session_id_on_session_created() {
        let event = Event::SessionCreated {
            connector_id: "conn-1".to_string(),
            session: minimal_session("ses-abc"),
        };
        let routing = EventRouting::derive(&event, None, "conn-1");
        assert_eq!(routing.native_session_id.as_deref(), Some("ses-abc"));
        assert_eq!(routing.kind, EventKind::SessionLifecycle);
    }

    #[test]
    fn derive_sets_kind_update_on_session_update() {
        use crate::SessionUpdate as SU;
        let event = Event::SessionUpdate {
            connector_id: "conn-1".to_string(),
            session_id: "ses-xyz".to_string(),
            update: SU::Unknown {
                data: serde_json::json!({"type": "unknown_future"}),
            },
        };
        let routing = EventRouting::derive(&event, None, "conn-1");
        assert_eq!(routing.kind, EventKind::Update);
        assert_eq!(routing.native_session_id.as_deref(), Some("ses-xyz"));
    }

    #[test]
    fn derive_sets_kind_message_for_message_started() {
        use crate::conversation::{Message, MessageRole, MessageStatus};
        let now = Utc::now();
        let msg = Message {
            id: "msg-1".to_string(),
            session_id: "ses-msg".to_string(),
            role: MessageRole::User,
            created_at: now,
            content: vec![],
            status: MessageStatus::Pending,
            metadata: None,
        };
        let event = Event::MessageStarted {
            connector_id: "conn-1".to_string(),
            message: msg,
        };
        let routing = EventRouting::derive(&event, None, "conn-1");
        assert_eq!(routing.kind, EventKind::Message);
        assert_eq!(routing.native_session_id.as_deref(), Some("ses-msg"));
    }

    #[test]
    fn derive_sets_kind_system_for_error() {
        let event = Event::Error {
            message: "something went wrong".to_string(),
        };
        let routing = EventRouting::derive(&event, None, "conn-1");
        assert_eq!(routing.kind, EventKind::System);
        assert!(routing.native_session_id.is_none());
    }

    #[test]
    fn event_origin_replay_roundtrips_replay_id() {
        let id = Uuid::new_v4();
        let origin = EventOrigin::Replay { replay_id: id };
        match &origin {
            EventOrigin::Replay { replay_id } => assert_eq!(*replay_id, id),
            _ => panic!("wrong variant"),
        }
    }

    #[test]
    fn from_connector_event_produces_connector_origin() {
        let event = Event::Connected;
        let uid = Uuid::nil();
        let bus = BusEvent::from_connector_event(event, Some(uid), "conn-test".to_string());
        match &bus.origin {
            EventOrigin::Connector {
                connector_uid,
                connector_id,
            } => {
                assert_eq!(*connector_uid, Some(uid));
                assert_eq!(connector_id, "conn-test");
            }
            _ => panic!("expected Connector origin"),
        }
        assert_eq!(bus.routing.connector_id.as_deref(), Some("conn-test"));
    }
}
