//! Storage operations for LocalEpisteme.
//!
//! Handles ingestion of claims, observations, and authoritative assertions.

use stemedb_core::types::Assertion;
use stemedb_ingest::serialize_assertion;
use stemedb_storage::PredicateIndexStore;
use tracing::{debug, info, instrument, warn};

use crate::bridge::{authored_claim_to_assertion, observation_to_assertion};
use crate::types::{predicates, AuthoredClaim, Observation};
use crate::walker::git::get_current_commit_hash;
use crate::AphoriaError;

use super::super::corpus::current_timestamp;
use super::LocalEpisteme;

impl LocalEpisteme {
    /// Ingest a batch of extracted claims into Episteme.
    ///
    /// **Deprecated:** Use `ingest_observations()` instead — this method accepts
    /// `&[Observation]`, not `&[AuthoredClaim]`, despite its name.
    #[deprecated(note = "Use ingest_observations() — this method name is misleading")]
    #[allow(dead_code)]
    #[instrument(skip(self, claims), fields(claim_count = claims.len()))]
    pub async fn ingest_claims(&self, claims: &[Observation]) -> Result<usize, AphoriaError> {
        let timestamp = current_timestamp();
        let mut ingested = 0;

        // Capture current git commit hash
        let git_commit = get_current_commit_hash(&self.project_root);
        if let Some(ref hash) = git_commit {
            debug!(git_commit = %hash, "Captured git commit for claim ingestion");
        }

        // Collect claims for predicate index updates
        let mut acknowledged_claims = Vec::new();
        let mut blessed_claims = Vec::new();

        for claim in claims {
            let assertion = observation_to_assertion(
                claim,
                &self.signing_key,
                timestamp,
                git_commit.as_deref(),
            );

            // Serialize and write to WAL
            let record_bytes = serialize_assertion(&assertion)
                .map_err(|e| AphoriaError::Storage(format!("Failed to serialize claim: {e}")))?;

            // Compute hash for predicate indexing (same as Ingestor uses)
            let hash = *blake3::hash(&record_bytes[8..]).as_bytes(); // Skip 8-byte header

            let mut journal = self.journal.lock().await;
            journal.append(record_bytes).map_err(|e| {
                AphoriaError::Storage(format!("Failed to append claim to WAL: {e}"))
            })?;

            // Track acknowledged claims for predicate index update
            if claim.predicate == predicates::ACKNOWLEDGED {
                acknowledged_claims.push(hash);
            }

            // Track blessed claims (created via `bless` command) for predicate index
            if claim.file == "aphoria_bless" {
                blessed_claims.push(hash);
            }

            debug!(
                concept_path = %claim.concept_path,
                predicate = %claim.predicate,
                "Ingested claim"
            );
            ingested += 1;
        }

        // Sync WAL
        {
            let mut journal = self.journal.lock().await;
            journal
                .force_sync()
                .map_err(|e| AphoriaError::Storage(format!("Failed to sync claims WAL: {e}")))?;
        }

        // Wait for ingestion to process
        self.ingestor.process_pending().await.map_err(|e| {
            AphoriaError::Storage(format!("Failed to process claims ingestion: {e}"))
        })?;

        // Update predicate index for acknowledged claims
        for hash in acknowledged_claims {
            if let Err(e) = self
                .predicate_index_store
                .add_to_predicate_index(predicates::ACKNOWLEDGED, &hash)
                .await
            {
                warn!(hash = %hex::encode(hash), error = %e, "Failed to add to predicate index");
            }
        }

        // Update predicate index for blessed claims
        for hash in blessed_claims {
            if let Err(e) =
                self.predicate_index_store.add_to_predicate_index(predicates::BLESSED, &hash).await
            {
                warn!(hash = %hex::encode(hash), error = %e, "Failed to add to blessed index");
            }
        }

        info!(ingested, "Ingested claims into Episteme");
        Ok(ingested)
    }

    /// Ingest code claims as Tier 4 (Community) observations.
    ///
    /// Used for claims that have no authority conflict — these become "project memory"
    /// that persists across commits and enables future drift detection.
    ///
    /// Returns the number of observations successfully ingested.
    #[instrument(skip(self, observations), fields(count = observations.len()))]
    pub async fn ingest_observations(
        &self,
        observations: &[Observation],
    ) -> Result<usize, AphoriaError> {
        if observations.is_empty() {
            return Ok(0);
        }

        let timestamp = current_timestamp();

        // Capture current git commit hash
        let git_commit = get_current_commit_hash(&self.project_root);
        if let Some(ref hash) = git_commit {
            debug!(git_commit = %hash, "Captured git commit for observation ingestion");
        }

        let mut count = 0;

        for claim in observations {
            let assertion = observation_to_assertion(
                claim,
                &self.signing_key,
                timestamp,
                git_commit.as_deref(),
            );

            // Serialize and write to WAL
            let record_bytes = serialize_assertion(&assertion).map_err(|e| {
                AphoriaError::Storage(format!("Failed to serialize observation: {e}"))
            })?;

            // Compute hash for predicate indexing
            let hash = *blake3::hash(&record_bytes[8..]).as_bytes(); // Skip 8-byte header

            let mut journal = self.journal.lock().await;
            journal.append(record_bytes).map_err(|e| {
                AphoriaError::Storage(format!("Failed to append observation to WAL: {e}"))
            })?;
            drop(journal);

            // Add to predicate index for "observation" queries
            if let Err(e) = self
                .predicate_index_store
                .add_to_predicate_index(predicates::OBSERVATION, &hash)
                .await
            {
                warn!(hash = %hex::encode(hash), error = %e, "Failed to add to observation index");
            }

            debug!(
                concept_path = %claim.concept_path,
                predicate = %claim.predicate,
                "Ingested observation"
            );
            count += 1;
        }

        // Sync WAL
        {
            let mut journal = self.journal.lock().await;
            journal.force_sync().map_err(|e| {
                AphoriaError::Storage(format!("Failed to sync observations WAL: {e}"))
            })?;
        }

        // Wait for ingestion to process
        self.ingestor.process_pending().await.map_err(|e| {
            AphoriaError::Storage(format!("Failed to process observations ingestion: {e}"))
        })?;

        info!(count, "Ingested observations as Tier 4 (project memory)");
        Ok(count)
    }

    /// Ingest authoritative assertions (RFC, OWASP, etc.).
    ///
    /// Writes assertions to WAL and adds them to the AUTHORITATIVE predicate index
    /// so they are discoverable by `fetch_authoritative_assertions()` during scans.
    #[instrument(skip(self, assertions), fields(count = assertions.len()))]
    pub async fn ingest_authoritative(
        &self,
        assertions: &[Assertion],
    ) -> Result<usize, AphoriaError> {
        let mut ingested = 0;
        let mut hashes = Vec::with_capacity(assertions.len());

        for assertion in assertions {
            let record_bytes = serialize_assertion(assertion).map_err(|e| {
                AphoriaError::Storage(format!(
                    "Failed to serialize authoritative assertion '{}': {e}",
                    assertion.subject
                ))
            })?;

            // Compute hash for predicate indexing (skip 8-byte header, same as Ingestor)
            let hash = *blake3::hash(&record_bytes[8..]).as_bytes();
            hashes.push(hash);

            let mut journal = self.journal.lock().await;
            journal.append(record_bytes).map_err(|e| {
                AphoriaError::Storage(format!(
                    "Failed to append authoritative assertion to WAL: {e}"
                ))
            })?;
            ingested += 1;
        }

        // Sync and process
        {
            let mut journal = self.journal.lock().await;
            journal.force_sync().map_err(|e| {
                AphoriaError::Storage(format!("Failed to sync authoritative WAL: {e}"))
            })?;
        }
        self.ingestor.process_pending().await.map_err(|e| {
            AphoriaError::Storage(format!("Failed to process authoritative ingestion: {e}"))
        })?;

        // Add all assertions to the AUTHORITATIVE predicate index
        // This mirrors the pattern from policy_ops.rs import_policy()
        for hash in &hashes {
            if let Err(e) = self
                .predicate_index_store
                .add_to_predicate_index(predicates::AUTHORITATIVE, hash)
                .await
            {
                warn!(hash = %hex::encode(hash), error = %e, "Failed to add to authoritative index");
            }
        }

        info!(ingested, "Ingested authoritative assertions");
        Ok(ingested)
    }

    /// Ingest an authored claim as a StemeDB assertion.
    ///
    /// Uses `authored_claim_to_assertion()` to convert, then writes to WAL.
    /// Indexed under `AUTHORED_CLAIM` predicate for efficient query.
    #[allow(dead_code)] // Used by EpistemeClaimStore (T4) and CLI handlers (T5)
    #[instrument(skip(self, claim), fields(claim_id = %claim.id, concept_path = %claim.concept_path))]
    pub async fn ingest_authored_claim(
        &self,
        claim: &AuthoredClaim,
    ) -> Result<[u8; 32], AphoriaError> {
        let timestamp = current_timestamp();
        let git_commit = get_current_commit_hash(&self.project_root);

        let assertion = authored_claim_to_assertion(
            claim,
            &self.signing_key,
            timestamp,
            git_commit.as_deref(),
        )?;

        let record_bytes = serialize_assertion(&assertion).map_err(|e| {
            AphoriaError::Storage(format!("Failed to serialize authored claim: {e}"))
        })?;

        let hash = *blake3::hash(&record_bytes[8..]).as_bytes();

        // Write to WAL
        {
            let mut journal = self.journal.lock().await;
            journal.append(record_bytes).map_err(|e| {
                AphoriaError::Storage(format!("Failed to append authored claim to WAL: {e}"))
            })?;
            journal.force_sync().map_err(|e| {
                AphoriaError::Storage(format!("Failed to sync authored claim WAL: {e}"))
            })?;
        }

        // Process through ingestor
        self.ingestor.process_pending().await.map_err(|e| {
            AphoriaError::Storage(format!("Failed to process authored claim ingestion: {e}"))
        })?;

        // Index under AUTHORED_CLAIM predicate
        if let Err(e) = self
            .predicate_index_store
            .add_to_predicate_index(predicates::AUTHORED_CLAIM, &hash)
            .await
        {
            warn!(hash = %hex::encode(hash), error = %e, "Failed to add to authored_claim index");
        }

        info!(claim_id = %claim.id, "Ingested authored claim into StemeDB");
        Ok(hash)
    }

    /// Ingest multiple authored claims in batch.
    ///
    /// Returns the number of claims successfully ingested.
    #[allow(dead_code)] // Used by import handler (T7)
    #[instrument(skip(self, claims), fields(count = claims.len()))]
    pub async fn ingest_authored_claims(
        &self,
        claims: &[AuthoredClaim],
    ) -> Result<usize, AphoriaError> {
        let mut ingested = 0;
        for claim in claims {
            match self.ingest_authored_claim(claim).await {
                Ok(_) => ingested += 1,
                Err(e) => {
                    warn!(claim_id = %claim.id, error = %e, "Failed to ingest authored claim");
                }
            }
        }
        info!(ingested, total = claims.len(), "Batch ingested authored claims");
        Ok(ingested)
    }

    /// Fetch all "acknowledged" assertions for policy export.
    pub async fn fetch_acknowledgments(&self) -> Result<Vec<Assertion>, AphoriaError> {
        self.fetch_assertions_by_predicate(predicates::ACKNOWLEDGED).await
    }

    /// Fetch all "blessed" assertions (authoritative patterns) for policy export.
    pub async fn fetch_blessed_assertions(&self) -> Result<Vec<Assertion>, AphoriaError> {
        self.fetch_assertions_by_predicate(predicates::BLESSED).await
    }

    /// Fetch all authoritative assertions imported from Trust Packs.
    ///
    /// These are assertions imported via `policy import` that should be used
    /// for conflict detection during scans. They are indexed under the
    /// "authoritative" predicate key.
    pub async fn fetch_authoritative_assertions(&self) -> Result<Vec<Assertion>, AphoriaError> {
        self.fetch_assertions_by_predicate(predicates::AUTHORITATIVE).await
    }

    /// Fetch assertions by predicate from the predicate index.
    pub(crate) async fn fetch_assertions_by_predicate(
        &self,
        predicate: &str,
    ) -> Result<Vec<Assertion>, AphoriaError> {
        let hashes = self.predicate_index_store.get_by_predicate(predicate).await.map_err(|e| {
            AphoriaError::Storage(format!(
                "Failed to fetch predicate index for '{}': {e}",
                predicate
            ))
        })?;

        let mut assertions = Vec::new();

        for hash in hashes {
            if let Some(assertion) = self.load_assertion_by_hash(&hash).await {
                assertions.push(assertion);
            }
        }

        info!(predicate, count = assertions.len(), "Fetched assertions by predicate");
        Ok(assertions)
    }
}
