//! Lifecycle methods for [`TidalDb`]: `close`, `shutdown`, `shutdown_inner`,
//! `Drop`, and `Debug` implementations.

use std::sync::atomic::Ordering;

use crate::schema::{DurabilityError, TidalError, Timestamp};

use super::TidalDb;
use super::text_syncer::shutdown_text_syncer;

impl TidalDb {
    // ── Lifecycle ─────────────────────────────────────────────────────────────

    /// Cleanly shut down the database.
    ///
    /// 1. Checkpoints all in-memory signal state to durable storage.
    /// 2. Flushes the storage engine.
    /// 3. Writes a WAL checkpoint marker and truncates old segments.
    /// 4. Shuts down the WAL writer thread.
    ///
    /// # Errors
    ///
    /// Returns an error if WAL shutdown fails.
    #[tracing::instrument(skip(self))]
    pub fn close(self) -> crate::Result<()> {
        self.shutdown_inner()
    }

    /// Alias for [`close`](Self::close). Cleanly shuts down the database.
    ///
    /// # Errors
    ///
    /// Returns an error if WAL shutdown fails.
    pub fn shutdown(self) -> crate::Result<()> {
        self.close()
    }

    /// Internal shutdown logic shared by `close()` and `Drop`.
    #[allow(clippy::too_many_lines)]
    pub(crate) fn shutdown_inner(&self) -> crate::Result<()> {
        // CAS: first caller to flip false -> true executes the shutdown body.
        if self
            .closed
            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
            .is_err()
        {
            // Already closed -- idempotent.
            return Ok(());
        }

        tracing::debug!(mode = ?self.config.mode, "tidaldb shutting down");

        // Mark health as degraded so the metrics endpoint reflects shutdown.
        self.metrics.health_ok.store(false, Ordering::Release);

        // M8p2: Join the segment receiver thread before WAL shuts down.
        // The receiver may hold Arc<SignalLedger> and the transport may block
        // in recv_segment until the sender side is dropped.
        if let Ok(mut guard) = self.receiver_handle.lock()
            && let Some(handle) = guard.take()
            && let Err(e) = handle.join()
        {
            tracing::warn!(error = %e, "segment receiver exited with corruption error");
        }

        // M7p2: Signal sweeper to stop and join the thread.
        // This must happen BEFORE WAL shutdown, because close_session_internal()
        // writes to the WAL.
        self.shutdown_sweeper.store(true, Ordering::Release);
        let sweeper_handle = {
            let mut guard = match self.sweeper_thread.lock() {
                Ok(g) => g,
                Err(poisoned) => poisoned.into_inner(),
            };
            guard.take()
        };
        if let Some(handle) = sweeper_handle {
            let _ = handle.join();
        }

        // M7p2: Final sweep for sessions that expired since the last scan.
        self.sweep_expired_sessions();

        // M7p2: Clean up remaining active sessions in-memory only.
        // We intentionally do NOT write Close WAL events here so that sessions
        // survive an unclean shutdown and are restored on the next open.
        // Agents that forget to call close_session() will have their sessions
        // replayed from WAL and restored as active on restart.
        let remaining: Vec<(crate::session::SessionId, String)> = self
            .sessions
            .iter()
            .map(|e| (*e.key(), e.value().agent_id.as_str().to_owned()))
            .collect();
        for (session_id, agent_id) in remaining {
            self.sessions.remove(&session_id);
            self.rate_limiter
                .remove(agent_id.as_str(), session_id.as_u64());
            tracing::debug!(
                session_id = %session_id,
                "shutdown: in-memory session cleanup (no Close WAL event)"
            );
        }

        // Stop the checkpoint thread before the final checkpoint to avoid races.
        self.shutdown_checkpoint.store(true, Ordering::Release);
        let checkpoint_handle = {
            let mut guard = match self.checkpoint_thread.lock() {
                Ok(g) => g,
                Err(poisoned) => poisoned.into_inner(),
            };
            guard.take()
        };
        if let Some(handle) = checkpoint_handle {
            let _ = handle.join();
        }

        // Shut down both text syncers (item + creator).
        shutdown_text_syncer(&self.text_tx, &self.text_syncer_thread, "text");
        shutdown_text_syncer(
            &self.creator_text_tx,
            &self.creator_text_syncer_thread,
            "creator text",
        );

        // Checkpoint signal state to storage.
        // Ordering: stop checkpoint thread -> checkpoint ledger -> shutdown WAL.
        // The `closed` flag prevents new writes; WAL replay on next open
        // re-applies any post-checkpoint events still in the WAL file.
        if let (Some(ledger), Some(storage)) = (&self.ledger, &self.storage) {
            let meta = crate::signals::checkpoint::CheckpointMeta {
                checkpoint_time_ns: Timestamp::now().as_nanos(),
                // Acquire pairs with the Release in WalHandleWriter::append_signal,
                // ensuring we see the highest seq committed by any WAL writer thread.
                wal_sequence: self.last_wal_seq.load(Ordering::Acquire),
                payload_hash: [0u8; 32], // computed by checkpoint()
            };
            if let Err(e) = ledger.checkpoint(storage.items_engine(), meta) {
                tracing::error!(error = %e, "signal ledger checkpoint failed during shutdown");
            }
            // Checkpoint cohort signal state (M6).
            if self.cohort_ledger.entry_count() > 0
                && let Err(e) = self.cohort_ledger.checkpoint(storage.items_engine(), meta)
            {
                tracing::error!(error = %e, "cohort ledger checkpoint failed during shutdown");
            }
            // Checkpoint co-engagement edges (M6).
            if self.co_engagement.edge_count() > 0
                && let Err(e) = self.co_engagement.checkpoint(storage.items_engine())
            {
                tracing::error!(error = %e, "co-engagement checkpoint failed during shutdown");
            }
            if let Err(e) = storage.flush() {
                tracing::error!(error = %e, "storage flush failed during shutdown");
            }
        }

        // 2. Shut down WAL (needs ownership via take()).
        let wal_opt = self
            .wal
            .lock()
            .map_err(|_| TidalError::internal("wal_access", "WAL mutex poisoned"))?
            .take();

        if let Some(wal) = wal_opt {
            let seq = self.last_wal_seq.load(Ordering::Acquire);
            if seq > 0 {
                // Write WAL checkpoint marker so the next open knows the replay
                // start point.
                if let Err(e) = wal.checkpoint(seq) {
                    tracing::error!(error = %e, "WAL checkpoint marker failed during shutdown");
                }
            }
            wal.shutdown().map_err(|e| {
                TidalError::Durability(DurabilityError {
                    message: format!("WAL shutdown failed: {e}"),
                })
            })?;

            // Compact WAL segments that are covered by the checkpoint.
            // Runs AFTER wal.shutdown() to avoid racing with the writer thread.
            // Compaction failure is non-fatal: segments survive and are replayed
            // redundantly on next open.
            if seq > 0
                && let Some(wal_dir) = self.config.data_dir.as_ref().map(|d| d.join("wal"))
                && let Err(e) = crate::wal::compaction::compact_wal(&wal_dir, seq)
            {
                tracing::warn!(error = %e, "WAL compaction failed during shutdown");
            }
        }

        Ok(())
    }
}

impl Drop for TidalDb {
    fn drop(&mut self) {
        // Stop metrics HTTP server if still running.
        #[cfg(feature = "metrics")]
        if let Some(ref mut handle) = self.metrics_handle {
            handle.stop();
        }

        if let Err(e) = self.shutdown_inner() {
            tracing::error!(error = %e, "error during tidaldb shutdown");
        }
    }
}

impl std::fmt::Debug for TidalDb {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("TidalDb")
            .field("mode", &self.config.mode)
            .field("closed", &self.closed.load(Ordering::Relaxed))
            .finish_non_exhaustive()
    }
}
