//! Source status enrichment for query results.
//!
//! This service provides batch lookup of source statuses and generates
//! warnings for assertions citing non-Active sources. It implements the
//! P3.2 "Cascade Flagging" feature which shows warnings instead of
//! silently filtering data.
//!
//! # Design
//!
//! - Batch lookups minimize database round-trips
//! - Warnings are attached at query time (not stored in assertions)
//! - Preserves append-only immutability of the assertion store

use std::collections::{HashMap, HashSet};
use std::sync::Arc;

use stemedb_core::types::{SourceRecord, SourceStatus};
use stemedb_storage::{GenericSourceRegistry, KVStore, SourceRegistry};

use crate::dto::SourceWarningDto;
use crate::error::ApiError;

/// Enriches assertions with source status information.
///
/// # Example
///
/// ```ignore
/// let enricher = SourceStatusEnricher::new(store.clone());
///
/// // Batch lookup source statuses
/// let statuses = enricher.batch_lookup(&source_hashes).await?;
///
/// // Check if should filter
/// if !should_exclude_source(statuses.get(&hash)) {
///     // Generate warning if needed
///     if let Some(warning) = make_source_warning(&record) {
///         assertion_dto.source_warning = Some(warning);
///     }
/// }
/// ```
pub struct SourceStatusEnricher<S> {
    registry: GenericSourceRegistry<S>,
}

impl<S: KVStore + 'static> SourceStatusEnricher<Arc<S>> {
    /// Create a new enricher backed by the given store.
    pub fn new(store: Arc<S>) -> Self {
        Self { registry: GenericSourceRegistry::new(store) }
    }

    /// Batch lookup source statuses for a set of source hashes.
    ///
    /// Deduplicates hashes before lookup to minimize I/O.
    /// Returns a map from hash to SourceRecord for found sources.
    /// Sources not in the registry are omitted from results.
    pub async fn batch_lookup(
        &self,
        source_hashes: &[[u8; 32]],
    ) -> Result<HashMap<[u8; 32], SourceRecord>, ApiError> {
        let unique: HashSet<_> = source_hashes.iter().collect();
        let mut results = HashMap::new();

        for hash in unique {
            if let Ok(Some(record)) = self.registry.get(hash).await {
                results.insert(*hash, record);
            }
        }
        Ok(results)
    }

    /// Check if assertion should be excluded (source is quarantined).
    ///
    /// Returns true only for quarantined sources. Deprecated sources
    /// are shown with warnings, not filtered.
    #[inline]
    pub fn should_exclude(record: Option<&SourceRecord>) -> bool {
        should_exclude_source(record)
    }

    /// Generate warning DTO for a non-Active source.
    ///
    /// Returns None for Active sources (no warning needed).
    /// Returns appropriate warning for Deprecated or Quarantined sources.
    #[inline]
    pub fn make_warning(record: &SourceRecord) -> Option<SourceWarningDto> {
        make_source_warning(record)
    }
}

/// Check if assertion should be excluded based on source status.
///
/// Returns true only for quarantined sources. Deprecated sources
/// are shown with warnings, not filtered.
pub fn should_exclude_source(record: Option<&SourceRecord>) -> bool {
    matches!(record.map(|r| r.status), Some(SourceStatus::Quarantined))
}

/// Generate warning DTO for a non-Active source.
///
/// Returns None for Active sources (no warning needed).
/// Returns appropriate warning for Deprecated or Quarantined sources.
pub fn make_source_warning(record: &SourceRecord) -> Option<SourceWarningDto> {
    match record.status {
        SourceStatus::Active => None,
        SourceStatus::Deprecated => Some(SourceWarningDto {
            warning_type: "deprecated".to_string(),
            message: format!(
                "Source '{}' is deprecated. Consider using updated alternatives.",
                record.label
            ),
            source_label: Some(record.label.clone()),
            status_updated_at: record.updated_at,
        }),
        SourceStatus::Quarantined => Some(SourceWarningDto {
            warning_type: "quarantined".to_string(),
            message: format!(
                "Source '{}' is quarantined and under review for quality issues.",
                record.label
            ),
            source_label: Some(record.label.clone()),
            status_updated_at: record.updated_at,
        }),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_make_warning_active_returns_none() {
        let record = SourceRecord::new([0u8; 32], "Test Source".to_string(), None, 0, 1000);

        assert!(make_source_warning(&record).is_none());
    }

    #[test]
    fn test_make_warning_deprecated() {
        let mut record = SourceRecord::new([0u8; 32], "Old Study".to_string(), None, 1, 1000);
        record.status = SourceStatus::Deprecated;
        record.updated_at = 2000;

        let warning = make_source_warning(&record);
        assert!(warning.is_some());

        let warning = warning.expect("should have warning");
        assert_eq!(warning.warning_type, "deprecated");
        assert!(warning.message.contains("deprecated"));
        assert_eq!(warning.source_label, Some("Old Study".to_string()));
        assert_eq!(warning.status_updated_at, 2000);
    }

    #[test]
    fn test_make_warning_quarantined() {
        let mut record = SourceRecord::new([0u8; 32], "Retracted Study".to_string(), None, 1, 1000);
        record.status = SourceStatus::Quarantined;
        record.updated_at = 3000;

        let warning = make_source_warning(&record);
        assert!(warning.is_some());

        let warning = warning.expect("should have warning");
        assert_eq!(warning.warning_type, "quarantined");
        assert!(warning.message.contains("quarantined"));
        assert!(warning.message.contains("under review"));
        assert_eq!(warning.source_label, Some("Retracted Study".to_string()));
        assert_eq!(warning.status_updated_at, 3000);
    }

    #[test]
    fn test_should_exclude_quarantined() {
        let mut record = SourceRecord::new([0u8; 32], "Test".to_string(), None, 0, 1000);
        record.status = SourceStatus::Quarantined;

        assert!(should_exclude_source(Some(&record)));
    }

    #[test]
    fn test_should_not_exclude_deprecated() {
        let mut record = SourceRecord::new([0u8; 32], "Test".to_string(), None, 0, 1000);
        record.status = SourceStatus::Deprecated;

        assert!(!should_exclude_source(Some(&record)));
    }

    #[test]
    fn test_should_not_exclude_active() {
        let record = SourceRecord::new([0u8; 32], "Test".to_string(), None, 0, 1000);

        assert!(!should_exclude_source(Some(&record)));
    }

    #[test]
    fn test_should_not_exclude_none() {
        assert!(!should_exclude_source(None));
    }
}
