//! 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 + ?Sized>(
        &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.
    ///
    /// Dispatches to each assigned shard. On single-node deployments all
    /// assignments resolve to the local shard, so behavior is identical to
    /// calling `signal()` directly. During a dual-write migration, the signal
    /// is written to both the source and target shard; writes to remote shards
    /// are logged for visibility until a transport is wired in by the caller.
    ///
    /// # 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).
        let assignments = self.tenant_router.write_assignments(tenant_id, entity_id)?;
        let local_shard = self.config.cluster.shard_id;
        for assignment in &assignments {
            if assignment.shard_id == local_shard {
                // Local shard: write directly to the signal ledger.
                self.signal(signal_type, entity_id, weight, timestamp)?;
            } else {
                // Remote shard (dual-write migration path): a transport-based dispatch
                // will be wired here once the shipper is integrated (Task 5).
                // For now we record the pending dispatch so it is visible to operators.
                tracing::debug!(
                    tenant_id = tenant_id.0,
                    shard_id = assignment.shard_id.0,
                    entity_id = entity_id.as_u64(),
                    signal_type,
                    "pending remote-shard dispatch (transport not yet wired)"
                );
            }
        }
        Ok(())
    }
}
