//! Claim storage interface and implementations.
//!
//! Provides persistence for human-authored claims via StemeDB assertions.
//! Claims are stored as content-addressed assertions in the append-only log,
//! indexed under the `AUTHORED_CLAIM` predicate for efficient queries.

use std::sync::Arc;

use tokio::runtime::Handle;

use crate::episteme::LocalEpisteme;
use crate::types::{AuthoredClaim, ClaimStatus};
use crate::AphoriaError;

/// Filter criteria for querying claims.
#[derive(Debug, Clone, Default)]
pub struct ClaimFilter {
    /// Filter by concept path (exact match)
    pub concept_path: Option<String>,

    /// Filter by predicate (exact match)
    pub predicate: Option<String>,

    /// Filter by authority tier (e.g., "expert", "regulatory").
    pub authority_tier: Option<String>,
}

impl ClaimFilter {
    /// Check if a claim matches this filter.
    pub fn matches(&self, claim: &AuthoredClaim) -> bool {
        if let Some(ref cp) = self.concept_path {
            if claim.concept_path != *cp {
                return false;
            }
        }
        if let Some(ref p) = self.predicate {
            if claim.predicate != *p {
                return false;
            }
        }
        if let Some(ref at) = self.authority_tier {
            if claim.authority_tier != *at {
                return false;
            }
        }
        true
    }
}

/// Statistics from bulk import operations.
#[derive(Debug, Default)]
pub struct ImportStats {
    /// Number of claims successfully imported
    pub imported: usize,

    /// Number of claims skipped (duplicates)
    pub skipped: usize,

    /// Number of claims that failed to import
    pub errors: usize,
}

/// Trait for claim storage backends.
///
/// Implementations provide persistence for `AuthoredClaim` instances.
/// The primary implementation is `EpistemeClaimStore` which stores claims
/// as StemeDB assertions in the append-only knowledge graph.
pub trait ClaimStore: Send + Sync {
    /// Save a new claim or update an existing one.
    ///
    /// In append-only mode, "updating" means appending a new assertion
    /// with the same concept_path + predicate. The most recent assertion
    /// wins at query time.
    fn save_claim(&self, claim: &AuthoredClaim) -> Result<(), AphoriaError>;

    /// Load a specific claim by concept path and predicate.
    fn load_claim(
        &self,
        concept_path: &str,
        predicate: &str,
    ) -> Result<Option<AuthoredClaim>, AphoriaError>;

    /// List all claims matching the filter criteria.
    ///
    /// If filter is empty (all fields None), returns all claims.
    fn list_claims(&self, filter: &ClaimFilter) -> Result<Vec<AuthoredClaim>, AphoriaError>;

    /// Delete a claim by concept path and predicate.
    ///
    /// In append-only mode, this creates a "deprecated" assertion.
    /// Returns `true` if a claim was deprecated, `false` if not found.
    fn delete_claim(&self, concept_path: &str, predicate: &str) -> Result<bool, AphoriaError>;

    /// Import multiple claims in bulk.
    ///
    /// Duplicates (same concept_path + predicate) are skipped.
    fn import_claims(&self, claims: &[AuthoredClaim]) -> Result<ImportStats, AphoriaError> {
        let mut stats = ImportStats::default();

        for claim in claims {
            match self.save_claim(claim) {
                Ok(()) => stats.imported += 1,
                Err(_) => stats.errors += 1,
            }
        }

        Ok(stats)
    }

    /// Export claims matching filter criteria.
    fn export_claims(&self, filter: &ClaimFilter) -> Result<Vec<AuthoredClaim>, AphoriaError> {
        self.list_claims(filter)
    }
}

/// StemeDB-backed claim storage.
///
/// Claims are stored as assertions in the local StemeDB instance.
/// Uses the `AUTHORED_CLAIM` predicate index for efficient queries.
///
/// This is the primary implementation of `ClaimStore`, replacing direct
/// TOML file access. Claims are append-only: updates create new assertions,
/// deletes create retired assertions.
pub struct EpistemeClaimStore {
    episteme: Arc<LocalEpisteme>,
    handle: Handle,
}

impl EpistemeClaimStore {
    /// Create a new StemeDB-backed claim store.
    pub fn new(episteme: Arc<LocalEpisteme>, handle: Handle) -> Self {
        Self { episteme, handle }
    }
}

impl ClaimStore for EpistemeClaimStore {
    fn save_claim(&self, claim: &AuthoredClaim) -> Result<(), AphoriaError> {
        self.handle.block_on(self.episteme.ingest_authored_claim(claim))?;
        Ok(())
    }

    fn load_claim(
        &self,
        concept_path: &str,
        predicate: &str,
    ) -> Result<Option<AuthoredClaim>, AphoriaError> {
        self.handle.block_on(self.episteme.fetch_authored_claim(concept_path, predicate))
    }

    fn list_claims(&self, filter: &ClaimFilter) -> Result<Vec<AuthoredClaim>, AphoriaError> {
        self.handle.block_on(self.episteme.fetch_authored_claims_filtered(filter))
    }

    fn delete_claim(&self, concept_path: &str, predicate: &str) -> Result<bool, AphoriaError> {
        // Append-only: create a "deprecated" assertion instead of deleting
        if let Some(mut claim) = self.load_claim(concept_path, predicate)? {
            claim.status = ClaimStatus::Deprecated;
            self.save_claim(&claim)?;
            Ok(true)
        } else {
            Ok(false)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{AuthoredClaim, AuthoredValue};

    #[test]
    fn test_claim_filter_empty_matches_all() {
        let filter = ClaimFilter::default();
        let claim = make_test_claim("test/path", "enabled", "expert");
        assert!(filter.matches(&claim));
    }

    #[test]
    fn test_claim_filter_concept_path() {
        let filter =
            ClaimFilter { concept_path: Some("test/path".to_string()), ..Default::default() };
        let matching = make_test_claim("test/path", "enabled", "expert");
        let non_matching = make_test_claim("other/path", "enabled", "expert");
        assert!(filter.matches(&matching));
        assert!(!filter.matches(&non_matching));
    }

    #[test]
    fn test_claim_filter_predicate() {
        let filter = ClaimFilter { predicate: Some("enabled".to_string()), ..Default::default() };
        let matching = make_test_claim("test/path", "enabled", "expert");
        let non_matching = make_test_claim("test/path", "disabled", "expert");
        assert!(filter.matches(&matching));
        assert!(!filter.matches(&non_matching));
    }

    #[test]
    fn test_claim_filter_authority_tier() {
        let filter =
            ClaimFilter { authority_tier: Some("regulatory".to_string()), ..Default::default() };
        let matching = make_test_claim("test/path", "enabled", "regulatory");
        let non_matching = make_test_claim("test/path", "enabled", "expert");
        assert!(filter.matches(&matching));
        assert!(!filter.matches(&non_matching));
    }

    #[test]
    fn test_claim_filter_combined() {
        let filter = ClaimFilter {
            concept_path: Some("test/path".to_string()),
            predicate: Some("enabled".to_string()),
            authority_tier: Some("expert".to_string()),
        };
        let matching = make_test_claim("test/path", "enabled", "expert");
        let wrong_path = make_test_claim("other/path", "enabled", "expert");
        let wrong_pred = make_test_claim("test/path", "disabled", "expert");
        let wrong_tier = make_test_claim("test/path", "enabled", "community");

        assert!(filter.matches(&matching));
        assert!(!filter.matches(&wrong_path));
        assert!(!filter.matches(&wrong_pred));
        assert!(!filter.matches(&wrong_tier));
    }

    fn make_test_claim(concept_path: &str, predicate: &str, tier: &str) -> AuthoredClaim {
        AuthoredClaim {
            id: "test-001".to_string(),
            concept_path: concept_path.to_string(),
            predicate: predicate.to_string(),
            value: AuthoredValue::Bool(true),
            comparison: Default::default(),
            provenance: "test".to_string(),
            invariant: "test".to_string(),
            consequence: "test".to_string(),
            authority_tier: tier.to_string(),
            evidence: vec![],
            category: "test".to_string(),
            status: ClaimStatus::Active,
            supersedes: None,
            created_by: "test".to_string(),
            created_at: "2026-02-12T00:00:00Z".to_string(),
            updated_at: None,
        }
    }
}
