//! Replication, multi-tenancy, and control-plane methods on [`TidalDb`].

use std::sync::Arc;

use super::TidalDb;

impl TidalDb {
    /// Access the replication state (for lag gauges and tests).
    #[must_use]
    #[allow(clippy::missing_const_for_fn)] // Arc field prevents const in practice
    pub fn replication_state(&self) -> &Arc<crate::replication::state::ReplicationState> {
        &self.replication_state
    }

    /// Start the segment receiver for a follower node.
    ///
    /// The receiver thread blocks on the transport's `recv_segment()` and
    /// applies each received WAL segment to the local signal ledger.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if no ledger is wired (ephemeral without schema).
    pub fn start_replication<T: crate::replication::Transport>(
        &self,
        transport: std::sync::Arc<T>,
    ) -> crate::Result<()> {
        let ledger = self.ledger()?.clone();
        let state = Arc::clone(&self.replication_state);
        let handle = crate::replication::receiver::spawn_receiver(transport, ledger, state);
        if let Ok(mut guard) = self.receiver_handle.lock() {
            *guard = Some(handle);
        }
        Ok(())
    }

    /// Returns the shared `TenantRouter` for multi-tenant routing.
    #[must_use]
    #[allow(clippy::missing_const_for_fn)]
    pub fn tenant_router(&self) -> Arc<crate::replication::TenantRouter> {
        Arc::clone(&self.tenant_router)
    }

    /// Returns a reference to the embedded control plane.
    #[must_use]
    #[allow(clippy::missing_const_for_fn)]
    pub fn control_plane(&self) -> &Arc<crate::replication::ControlPlane> {
        &self.control_plane
    }

    /// Create a `RollingUpgradeCoordinator` backed by this instance's control plane.
    #[must_use]
    pub fn rolling_upgrade_coordinator(&self) -> crate::replication::RollingUpgradeCoordinator {
        crate::replication::RollingUpgradeCoordinator::new(Arc::clone(&self.control_plane))
    }

    /// Write a signal on behalf of a tenant, enforcing per-tenant rate limits.
    ///
    /// # Errors
    ///
    /// - `TidalError::QuotaExceeded` — tenant rate limit exceeded.
    /// - `TidalError::Internal` — no eligible shards for the tenant.
    /// - Any error from the underlying `signal()` call.
    pub fn signal_for_tenant(
        &self,
        tenant_id: crate::replication::TenantId,
        signal_type: &str,
        entity_id: crate::schema::EntityId,
        weight: f64,
        timestamp: crate::schema::Timestamp,
    ) -> crate::Result<()> {
        self.require_writeable("signal_for_tenant")?;
        if let Some(limiter) = self.tenant_router.rate_limiter_for(tenant_id) {
            limiter.try_acquire()?;
        }
        // Resolve all shard targets (1 in normal mode; 2 during dual-write migration).
        // On single-node deployments all assignments resolve to local storage, so the
        // write happens once — but the routing contract is validated and recorded.
        let _assignments = self.tenant_router.write_assignments(tenant_id, entity_id)?;
        self.signal(signal_type, entity_id, weight, timestamp)
    }
}
