//! # event_sink — durable-lifecycle seam (slice-5, review M3)
//!
//! ADR-0005 promises call lifecycle → Valkey streams → durable CDR
//! pipeline. Until that lands, lifecycle events die with the process —
//! an OOM-killed node erases all evidence of its calls. This module is
//! the SEAM: the media thread emits `CallEvent`s through `EventSink`;
//! today's impl logs, the ADR-0005 impl will publish. The emission
//! points and the event shape are the part that calcifies — the backend
//! is plumbing.

use std::sync::Arc;
use std::time::SystemTime;

use rutster_call_model::ChannelId;
use rutster_tap::MetricsSnapshot;

/// CDR-shaped lifecycle events. `SessionEnded` carries both timestamps so
/// a consumer computes duration without correlating two events (a node
/// can die between them — the whole point of emitting durably).
#[derive(Debug, Clone)]
pub enum CallEvent {
    SessionRegistered {
        id: ChannelId,
        at: SystemTime,
    },
    SessionConnected {
        id: ChannelId,
        at: SystemTime,
    },
    SessionEnded {
        id: ChannelId,
        started_at: SystemTime,
        ended_at: SystemTime,
        reason: EndReason,
        /// Tap counters at teardown — fulfills the tap_engine.rs promise
        /// ("read a MetricsSnapshot for log/CDR emission on teardown").
        /// Snapshot is taken just before the engine task is torn down, so
        /// counts may trail the final frame by a tick — fine for a CDR.
        tap_metrics: Option<MetricsSnapshot>,
    },
}

/// Why the session ended — the CDR disposition embryo.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EndReason {
    /// DELETE /v1/sessions/:id (API-driven hangup).
    Deleted,
    /// The session closed itself (peer close / idle timeout).
    Closed,
    /// Node shutdown dropped it (the mass-hangup path drain exists to avoid).
    Shutdown,
}

/// The seam. CONTRACT: `emit` is called from the media std::thread —
/// implementations MUST NOT block or perform I/O inline. Buffer to your
/// own task (the future Valkey impl does channel → tokio publisher).
pub trait EventSink: Send + Sync {
    fn emit(&self, event: CallEvent);

    /// Count of events dropped because the sink's bounded queue was full
    /// or the background task has died. `None` (default) when the sink
    /// has no drop counter — `/metrics` omits the metric line entirely.
    ///
    /// This is a trait *default method*: impls that don't override it
    /// automatically get the `None` behavior. Rust learners: this is how
    /// you extend a trait without breaking every existing implementation.
    fn drops(&self) -> Option<u64> {
        None
    }
}

/// Log-backed sink: structured tracing on target "rutster::events". Not
/// durable — the placeholder that makes the emission points real today.
pub struct TracingEventSink;

impl EventSink for TracingEventSink {
    fn emit(&self, event: CallEvent) {
        tracing::info!(target: "rutster::events", event = ?event, "call event");
    }
}

/// Convenience alias for the opts field / spawn signature.
pub type SharedEventSink = Arc<dyn EventSink>;
