//! Background worker that tails the WAL and updates the KV store.

mod processing;
mod record_types;
mod run;
mod storage;

#[cfg(test)]
mod tests;

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use stemedb_core::types::HlcTimestamp;
use stemedb_storage::{
    GenericIndexStore, GenericVoteStore, KVStore, MemTable, VectorIndex, VisualIndex,
};
use stemedb_wal::{Journal, HEADER_SIZE};
use tokio::sync::{Mutex, Notify};
use tracing::{debug, info, warn};

use crate::error::{IngestError, Result};
use crate::gossip::GossipBroadcast;

pub use record_types::{serialize_assertion, serialize_epoch, serialize_vote, RecordType};

/// Background worker that tails the WAL and updates the KV store.
pub struct IngestWorker<S> {
    journal: Arc<Mutex<Journal>>,
    store: Arc<S>,
    index_store: GenericIndexStore<Arc<S>>,
    vote_store: GenericVoteStore<Arc<S>>,
    current_offset: u64,
    notify: Option<Arc<Notify>>,
    vector_index: Option<Arc<dyn VectorIndex>>,
    visual_index: Option<Arc<dyn VisualIndex>>,
    shutdown: Arc<AtomicBool>,
    hlc: uhlc::HLC,
    gossip_broadcaster: Option<Arc<dyn GossipBroadcast>>,
    /// MemTable for read-your-writes eviction signaling.
    /// When assertions are indexed, we signal the MemTable to evict them.
    memtable: Option<Arc<MemTable>>,
    /// DEBUG ONLY: Skip signature verification for demos/testing
    pub skip_signature_verification: bool,
}

impl<S: KVStore + 'static> IngestWorker<S> {
    /// Create a new ingest worker.
    pub async fn new(journal: Arc<Mutex<Journal>>, store: Arc<S>) -> Result<Self> {
        let index_store = GenericIndexStore::new(store.clone());
        let vote_store = GenericVoteStore::new(store.clone());
        let cursor_key = stemedb_storage::key_codec::cursor_key();
        let current_offset = match store.get(&cursor_key).await? {
            Some(bytes) if bytes.len() == 8 => {
                let offset =
                    u64::from_le_bytes(bytes.try_into().map_err(|_| {
                        IngestError::Serialization("Invalid cursor bytes".to_string())
                    })?);
                info!(offset, "Resuming ingestion from persisted cursor");
                offset
            }
            Some(bytes) => {
                warn!(len = bytes.len(), "Corrupt cursor value, starting from beginning");
                HEADER_SIZE as u64
            }
            None => {
                debug!("No cursor found, starting from WAL header");
                HEADER_SIZE as u64
            }
        };
        let hlc = uhlc::HLCBuilder::new().build();

        Ok(Self {
            journal,
            store,
            index_store,
            vote_store,
            current_offset,
            notify: None,
            vector_index: None,
            visual_index: None,
            shutdown: Arc::new(AtomicBool::new(false)),
            hlc,
            gossip_broadcaster: None,
            memtable: None,
            skip_signature_verification: false,
        })
    }

    /// Create with shutdown signal.
    pub async fn with_shutdown(
        journal: Arc<Mutex<Journal>>,
        store: Arc<S>,
        shutdown: Arc<AtomicBool>,
    ) -> Result<Self> {
        let mut worker = Self::new(journal, store).await?;
        worker.shutdown = shutdown;
        Ok(worker)
    }

    /// Is shutdown?
    pub fn is_shutdown(&self) -> bool {
        self.shutdown.load(Ordering::Relaxed)
    }

    /// With notify.
    pub fn with_notify(mut self, notify: Arc<Notify>) -> Self {
        self.notify = Some(notify);
        self
    }

    /// With flush notify.
    pub fn with_flush_notify(mut self, notify: Arc<Notify>) -> Self {
        self.notify = Some(notify);
        self
    }

    /// With vector index.
    pub fn with_vector_index(mut self, index: Arc<dyn VectorIndex>) -> Self {
        self.vector_index = Some(index);
        self
    }

    /// With visual index.
    pub fn with_visual_index(mut self, index: Arc<dyn VisualIndex>) -> Self {
        self.visual_index = Some(index);
        self
    }

    /// With node ID.
    pub fn with_node_id(mut self, node_id: uhlc::ID) -> Self {
        self.hlc = uhlc::HLCBuilder::new().with_id(node_id).build();
        self
    }

    /// With gossip.
    pub fn with_gossip_broadcaster(mut self, broadcaster: Arc<dyn GossipBroadcast>) -> Self {
        self.gossip_broadcaster = Some(broadcaster);
        self
    }

    /// With MemTable for read-your-writes eviction signaling.
    ///
    /// When assertions are indexed in KVStore, the IngestWorker signals
    /// the MemTable to evict them, freeing memory.
    pub fn with_memtable(mut self, memtable: Arc<MemTable>) -> Self {
        self.memtable = Some(memtable);
        self
    }

    /// Get gossip.
    pub fn gossip_broadcaster(&self) -> Option<&Arc<dyn GossipBroadcast>> {
        self.gossip_broadcaster.as_ref()
    }

    /// Gen HLC.
    pub fn generate_hlc_timestamp(&self) -> HlcTimestamp {
        HlcTimestamp::now(&self.hlc)
    }

    /// Update HLC.
    pub fn update_hlc_from_remote(&self, remote: &HlcTimestamp) -> Result<()> {
        if let Some(ts) = remote.to_uhlc() {
            self.hlc.update_with_timestamp(&ts).map_err(|e| {
                warn!(remote_time = remote.time_ntp64, error = %e, "HLC update failed");
                IngestError::InputValidation(format!("HLC update failed: {}", e))
            })?;
        }
        Ok(())
    }

    /// Get HLC node ID.
    pub fn hlc_node_id(&self) -> [u8; 16] {
        self.hlc.get_id().to_le_bytes()
    }

    /// DEBUG ONLY: Enable skipping signature verification.
    pub fn with_skip_signature_verification(mut self, skip: bool) -> Self {
        self.skip_signature_verification = skip;
        self
    }
}
