//! Record processing methods for the IngestWorker. Ingests assertions, votes,
//! and epochs with validation and signature verification.

use super::record_types::RECORD_HEADER_SIZE;
use super::{IngestWorker, RecordType};
use crate::error::{IngestError, Result};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use stemedb_core::serde::deserialize;
use stemedb_core::signing::compute_content_hash_v2;
use stemedb_core::types::{Assertion, Epoch, Hash, Vote};
use stemedb_storage::key_codec;
use stemedb_storage::{IndexStore, KVStore, VoteStore};
use tracing::{debug, info, warn};

impl<S: KVStore + 'static> IngestWorker<S> {
    /// Process the next record from the WAL, returning bytes read (0 = no data).
    pub async fn step(&mut self) -> Result<u64> {
        let record = {
            let mut journal = self.journal.lock().await;
            debug!(offset = self.current_offset, "Attempting to read from WAL");
            match journal.read(self.current_offset) {
                Ok(record) => {
                    debug!(
                        offset = self.current_offset,
                        payload_len = record.payload.len(),
                        "Successfully read record from WAL"
                    );
                    record
                }
                Err(stemedb_wal::QuarantineError::Io { source, .. })
                    if source.kind() == std::io::ErrorKind::UnexpectedEof =>
                {
                    // True EOF - no more data to read
                    debug!(offset = self.current_offset, "WAL read returned EOF (Io variant)");
                    return Ok(0);
                }
                Err(stemedb_wal::QuarantineError::Io { source, .. })
                    if source.kind() == std::io::ErrorKind::NotFound =>
                {
                    // WAL file doesn't exist yet - this is valid, just no data to read
                    debug!("WAL file not found (not created yet), waiting for writes");
                    return Ok(0);
                }
                Err(stemedb_wal::QuarantineError::Io { path, source }) => {
                    // Real I/O error - log and propagate
                    tracing::error!(
                        path = %path.display(),
                        error = %source,
                        "WAL read I/O error"
                    );
                    return Err(IngestError::Wal(stemedb_wal::QuarantineError::Io {
                        path,
                        source,
                    }));
                }
                Err(stemedb_wal::QuarantineError::IoGeneric(e))
                    if e.kind() == std::io::ErrorKind::UnexpectedEof =>
                {
                    // True EOF - no more data to read
                    debug!(
                        offset = self.current_offset,
                        error = %e,
                        "WAL read returned EOF (IoGeneric variant)"
                    );
                    return Ok(0);
                }
                Err(e) => {
                    // Other WAL errors - log and propagate
                    tracing::error!(error = %e, "WAL read error");
                    return Err(IngestError::Wal(e));
                }
            }
        };

        let bytes_read = record.disk_size();

        // Extract record type from header (8-byte aligned)
        if record.payload.len() < RECORD_HEADER_SIZE {
            return Err(IngestError::Serialization("Payload too small for header".to_string()));
        }

        let record_type = RecordType::try_from(record.payload[0])?;
        let data = &record.payload[RECORD_HEADER_SIZE..];

        match record_type {
            RecordType::Assertion => self.ingest_assertion(data).await?,
            RecordType::Vote => self.ingest_vote(data).await?,
            RecordType::Epoch => self.ingest_epoch(data).await?,
        }

        let prev_offset = self.current_offset;
        self.current_offset += bytes_read;

        // Persist the cursor so recovery can skip already-processed records.
        // This is safe even if it fails: the write path is idempotent
        // (content-addressed keys), so re-processing a record is a no-op.
        let cursor_key = key_codec::cursor_key();
        self.store.put(&cursor_key, &self.current_offset.to_le_bytes()).await?;

        info!(
            record_type = ?record_type,
            offset = prev_offset,
            new_offset = self.current_offset,
            "Ingested record"
        );

        // Signal downstream consumers (e.g., Materializer) that new data is available
        if let Some(ref notify) = self.notify {
            notify.notify_one();
        }

        Ok(bytes_read)
    }

    /// Maximum allowed subject length in bytes.
    const MAX_SUBJECT_LEN: usize = 1024;
    /// Maximum allowed predicate length in bytes.
    const MAX_PREDICATE_LEN: usize = 256;

    /// Ingest an assertion into the KV store.
    ///
    /// Validates input bounds and verifies all Ed25519 signatures before storing.
    /// Rejects assertions with invalid or missing signatures.
    async fn ingest_assertion(&self, data: &[u8]) -> Result<()> {
        let assertion: Assertion =
            deserialize(data).map_err(|e| IngestError::Serialization(e.to_string()))?;

        // Validate input bounds before proceeding
        self.validate_assertion(&assertion)?;

        // Verify all signatures before storing
        // For v2, this computes the canonical hash internally (assertion with empty signatures)
        self.verify_assertion_signatures(&assertion)?;

        // Content-addressed key: {subject}\x00H:{BLAKE3_hash}
        let hash = blake3::hash(data);
        let hash_hex = hash.to_hex().to_string();
        let key = key_codec::assertion_key(&assertion.subject, &hash_hex);

        debug!(
            subject = %assertion.subject,
            predicate = %assertion.predicate,
            hash = %hash_hex,
            signature_count = assertion.signatures.len(),
            "Ingesting verified assertion"
        );

        // Store the assertion
        self.store.put(&key, data).await?;

        // Write reverse index: \x00HASH_SUBJECT:{hash_hex} -> subject
        let reverse_key = key_codec::hash_subject_key(&hash_hex);
        self.store.put(&reverse_key, assertion.subject.as_bytes()).await?;

        // Write subject discovery index: \x00SUBJECTS:{subject} -> empty
        let subjects_key = key_codec::subjects_index_key(&assertion.subject);
        self.store.put(&subjects_key, &[]).await?;

        // Increment assertion count: \x00META:assertion_count
        let count_key = key_codec::assertion_count_key();
        self.store.fetch_and_add_u64(&count_key, 1).await?;

        // Update indexes: {subject}\x00S: and {subject}\x00SP:{predicate}
        let assertion_hash: Hash = *hash.as_bytes();
        self.index_store
            .add_to_indexes(&assertion.subject, &assertion.predicate, &assertion_hash)
            .await?;

        // Add to source reverse index for impact analysis (P3.1)
        // This enables O(1) lookup of "which assertions cite this source?"
        self.index_store.add_to_source_index(&assertion.source_hash, &assertion_hash).await?;

        // Insert into vector index if present and assertion has a vector
        if let (Some(ref vector_index), Some(ref vector)) = (&self.vector_index, &assertion.vector)
        {
            if let Err(e) = vector_index.insert(&assertion_hash, vector) {
                // Log but don't fail the ingestion - vector index is supplementary
                warn!(
                    hash = %hash_hex,
                    error = %e,
                    "Failed to insert into vector index"
                );
            } else {
                debug!(
                    hash = %hash_hex,
                    dim = vector.len(),
                    "Inserted into vector index"
                );
            }
        }

        // Insert into visual index if present and assertion has a visual_hash
        if let (Some(ref visual_index), Some(ref phash)) =
            (&self.visual_index, &assertion.visual_hash)
        {
            if let Err(e) = visual_index.insert(&assertion_hash, phash) {
                // Log but don't fail the ingestion - visual index is supplementary
                warn!(
                    hash = %hash_hex,
                    error = %e,
                    "Failed to insert into visual index"
                );
            } else {
                debug!(
                    hash = %hash_hex,
                    phash = %hex::encode(phash),
                    "Inserted into visual index"
                );
            }
        }

        // Broadcast to peers if gossip is configured
        if let Some(ref broadcaster) = self.gossip_broadcaster {
            if broadcaster.is_enabled() {
                let hlc = self.generate_hlc_timestamp();
                if let Err(e) = broadcaster.broadcast(&assertion_hash, data, &hlc).await {
                    // Log but don't fail - gossip is best-effort
                    warn!(
                        hash = %hash_hex,
                        error = %e,
                        "Failed to broadcast assertion to peers"
                    );
                } else {
                    debug!(
                        hash = %hash_hex,
                        "Broadcast assertion to peers"
                    );
                }
            }
        }

        Ok(())
    }

    /// Validate assertion input bounds before storage.
    ///
    /// Rejects assertions with:
    /// - Confidence outside [0.0, 1.0] or NaN/Inf
    /// - Subject exceeding MAX_SUBJECT_LEN bytes
    /// - Predicate exceeding MAX_PREDICATE_LEN bytes
    /// - Subject containing null byte (would corrupt key boundaries)
    /// - Timestamp more than 1 hour in the future (clock skew protection)
    fn validate_assertion(&self, assertion: &Assertion) -> Result<()> {
        // Validate subject does not contain separator byte
        key_codec::validate_subject(&assertion.subject)
            .map_err(|e| IngestError::InputValidation(format!("invalid subject: {}", e)))?;

        // Validate confidence: must be finite and in [0.0, 1.0]
        if assertion.confidence.is_nan() {
            return Err(IngestError::InputValidation(
                "confidence is NaN (not a number)".to_string(),
            ));
        }
        if assertion.confidence.is_infinite() {
            return Err(IngestError::InputValidation(format!(
                "confidence is infinite: {}",
                assertion.confidence
            )));
        }
        if assertion.confidence < 0.0 || assertion.confidence > 1.0 {
            return Err(IngestError::InputValidation(format!(
                "confidence {} out of range [0.0, 1.0]",
                assertion.confidence
            )));
        }

        // Validate subject length
        if assertion.subject.len() > Self::MAX_SUBJECT_LEN {
            return Err(IngestError::InputValidation(format!(
                "subject exceeds {} bytes (got {})",
                Self::MAX_SUBJECT_LEN,
                assertion.subject.len()
            )));
        }

        // Validate predicate length
        if assertion.predicate.len() > Self::MAX_PREDICATE_LEN {
            return Err(IngestError::InputValidation(format!(
                "predicate exceeds {} bytes (got {})",
                Self::MAX_PREDICATE_LEN,
                assertion.predicate.len()
            )));
        }

        // Validate timestamp: reject if more than 1 hour in future (clock skew protection)
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0);
        let max_future = now + 3600; // 1 hour clock skew allowance
        if assertion.timestamp > max_future {
            return Err(IngestError::InputValidation(format!(
                "timestamp {} is more than 1 hour in the future (now: {}, max: {})",
                assertion.timestamp, now, max_future
            )));
        }

        Ok(())
    }

    /// Verify all Ed25519 signatures on an assertion.
    ///
    /// Supports two signature versions:
    /// - Version 1 (legacy): signs `"{subject}:{predicate}"` - only protects those fields
    /// - Version 2 (enterprise): signs the BLAKE3 content hash - protects ALL fields
    ///
    /// For v2 signatures, the content hash is computed from the assertion with
    /// empty signatures (canonical form), so tampering with any field except
    /// signatures will invalidate the signature.
    ///
    /// All signatures must be valid for the assertion to be accepted.
    fn verify_assertion_signatures(&self, assertion: &Assertion) -> Result<()> {
        if assertion.signatures.is_empty() {
            warn!(
                subject = %assertion.subject,
                predicate = %assertion.predicate,
                "Assertion has no signatures"
            );
            return Err(IngestError::InvalidSignature(
                "Assertion must have at least one signature".to_string(),
            ));
        }

        // Pre-compute v1 message (subject:predicate) - only used if v1 signatures exist
        let v1_message = format!("{}:{}", assertion.subject, assertion.predicate);

        // Pre-compute v2 content hash using the shared utility from stemedb-core.
        // This must match exactly what the signing code uses in compute_content_hash_v2().
        // The hash covers: subject, predicate, object, source_hash, source_class, confidence, timestamp.
        let v2_content_hash: Option<[u8; 32]> =
            if assertion.signatures.iter().any(|s| s.version == 2) {
                // Debug: show exact number format for comparison with signing
                let object_str = match &assertion.object {
                    stemedb_core::types::ObjectValue::Number(n) => format!("Number({:.17})", n),
                    other => format!("{:?}", other),
                };
                let confidence_str = format!("{:.17}", assertion.confidence);
                let hash = compute_content_hash_v2(assertion);
                debug!(
                    subject = %assertion.subject,
                    predicate = %assertion.predicate,
                    object = %object_str,
                    source_hash = %hex::encode(assertion.source_hash),
                    source_class = ?assertion.source_class,
                    confidence = %confidence_str,
                    timestamp = %assertion.timestamp,
                    content_hash = %hex::encode(hash),
                    "Computed v2 content hash for verification"
                );
                Some(hash)
            } else {
                None
            };

        for (idx, sig_entry) in assertion.signatures.iter().enumerate() {
            // Determine which message was signed based on version
            let message_bytes: &[u8] = match sig_entry.version {
                1 => {
                    // v1 (legacy): signs "{subject}:{predicate}"
                    v1_message.as_bytes()
                }
                2 => {
                    // v2 (enterprise): signs the content hash computed by compute_content_hash_v2
                    v2_content_hash.as_ref().ok_or_else(|| {
                        IngestError::InvalidSignature(
                            "v2 signature present but v2_content_hash was not computed".to_string(),
                        )
                    })?
                }
                v => {
                    return Err(IngestError::InvalidSignature(format!(
                        "Unknown signature version {} for signature {}",
                        v, idx
                    )));
                }
            };

            // Reconstruct the verifying key from the stored agent_id
            let verifying_key = VerifyingKey::from_bytes(&sig_entry.agent_id).map_err(|e| {
                IngestError::InvalidSignature(format!(
                    "Invalid public key for signature {}: {}",
                    idx, e
                ))
            })?;

            // Reconstruct the signature
            let signature = Signature::from_bytes(&sig_entry.signature);

            // Verify the signature
            verifying_key.verify(message_bytes, &signature).map_err(|e| {
                IngestError::InvalidSignature(format!(
                    "Signature {} (v{}) failed verification: {}",
                    idx, sig_entry.version, e
                ))
            })?;

            debug!(
                agent_id = %hex::encode(&sig_entry.agent_id[..8]),
                signature_idx = idx,
                version = sig_entry.version,
                "Signature verified"
            );
        }

        Ok(())
    }

    /// Ingest a vote into the KV store via VoteStore.
    ///
    /// Validates vote weight bounds (0.0 to 1.0, no NaN/Inf) and uses VoteStore
    /// to maintain vote count and aggregate weight caches automatically.
    /// This ensures VoteAwareConsensusLens has accurate data.
    ///
    /// Looks up the assertion's subject from the reverse index to co-locate
    /// vote data with the assertion for range sharding.
    async fn ingest_vote(&self, data: &[u8]) -> Result<()> {
        let vote: Vote =
            deserialize(data).map_err(|e| IngestError::Serialization(e.to_string()))?;

        // Validate vote weight: must be finite and in [0.0, 1.0]
        if vote.weight.is_nan() {
            return Err(IngestError::InputValidation(
                "vote weight is NaN (not a number)".to_string(),
            ));
        }
        if vote.weight.is_infinite() {
            return Err(IngestError::InputValidation(format!(
                "vote weight is infinite: {}",
                vote.weight
            )));
        }
        if vote.weight < 0.0 || vote.weight > 1.0 {
            return Err(IngestError::InputValidation(format!(
                "weight {} out of range [0.0, 1.0]",
                vote.weight
            )));
        }

        // Look up the subject from the reverse index
        let hash_hex = hex::encode(vote.assertion_hash);
        let reverse_key = key_codec::hash_subject_key(&hash_hex);
        let subject = match self.store.get(&reverse_key).await? {
            Some(bytes) => String::from_utf8(bytes).map_err(|e| {
                IngestError::Serialization(format!("Invalid subject in reverse index: {}", e))
            })?,
            None => {
                warn!(
                    assertion_hash = %hash_hex,
                    "Vote references unknown assertion (no reverse index entry)"
                );
                return Err(IngestError::InputValidation(format!(
                    "vote references unknown assertion {}",
                    hash_hex
                )));
            }
        };

        debug!(
            assertion_hash = %hash_hex,
            subject = %subject,
            weight = vote.weight,
            "Ingesting vote via VoteStore"
        );

        // Delegate to VoteStore which handles:
        // 1. Content-addressed storage at {subject}\x00V:{assertion_hex}:{vote_hex}
        // 2. Vote count cache at {subject}\x00VC:{assertion_hex}
        // 3. Aggregate weight cache at {subject}\x00VW:{assertion_hex}
        self.vote_store.put_vote(&vote, &subject).await?;

        Ok(())
    }

    /// Ingest an epoch into the KV store.
    ///
    /// Stores the epoch at `\x00E:{epoch_id}` and writes
    /// `\x00SUPERSEDED:{old_epoch_id}` marker keys for the full transitive closure
    /// of superseded epochs. This enables O(1) "is superseded?" lookups at
    /// query time instead of O(chain_length) chain walks.
    async fn ingest_epoch(&self, data: &[u8]) -> Result<()> {
        let epoch: Epoch =
            deserialize(data).map_err(|e| IngestError::Serialization(e.to_string()))?;

        // Epoch key: \x00E:{epoch_id_hash}
        let epoch_id_hex = hex::encode(epoch.id);
        let key = key_codec::epoch_key(&epoch_id_hex);

        debug!(
            epoch_id = %epoch_id_hex,
            name = %epoch.name,
            supersedes = ?epoch.supersedes.map(hex::encode),
            "Ingesting epoch"
        );

        self.store.put(&key, data).await?;

        // Write supersession cascade markers for O(1) query-time lookups
        if let Some(superseded_id) = epoch.supersedes {
            self.write_supersession_cascade(&epoch.id, &superseded_id).await?;
        }

        Ok(())
    }
}
