//! Result types for scan operations.

use std::fmt;
use std::path::Path;

use stemedb_core::types::SourceClass;

use stemedb_core::types::ObjectValue;

use super::claim::{ConflictingSource, ExtractedClaim};
use super::verdict::Verdict;

/// Result of a scan operation.
#[derive(Debug, Clone)]
pub struct ScanResult {
    /// Project name.
    pub project: String,

    /// Scan ID (for baseline comparison).
    pub scan_id: String,

    /// Number of files scanned.
    pub files_scanned: usize,

    /// Number of claims extracted.
    pub claims_extracted: usize,

    /// Conflicts found (authority conflicts).
    pub conflicts: Vec<ConflictResult>,

    /// Drifts detected (value changed from prior observation).
    pub drifts: Vec<DriftResult>,

    /// Output format.
    pub format: String,

    /// Whether debug traces are included.
    pub debug: bool,

    /// Number of Tier 4 observations recorded (when --sync is enabled).
    /// These are claims with no authority conflict that become "project memory".
    pub observations_recorded: usize,

    /// Benchmark timing breakdown (only populated when --benchmark is set).
    pub timing: Option<ScanTiming>,

    /// Extracted claims (only populated when --show-claims is enabled).
    ///
    /// When present, contains all claims extracted during the scan, sorted by
    /// file path and line number for easy verification and debugging.
    pub claims: Option<Vec<ExtractedClaim>>,

    /// Deprecated pattern usages detected.
    ///
    /// Populated when deprecated patterns are matched during scan.
    /// These generate FLAG warnings with migration guidance.
    pub deprecated_usages: Vec<DeprecatedUsageResult>,
}

/// Timing breakdown for benchmark mode.
///
/// Captures timing for each phase of the scan process to help identify
/// performance bottlenecks and measure scan speed on large codebases.
#[derive(Debug, Clone)]
pub struct ScanTiming {
    /// Time spent walking the project (discovering files) in milliseconds.
    pub walk_ms: u64,

    /// Time spent extracting claims from files in milliseconds.
    pub extraction_ms: u64,

    /// Time spent checking conflicts against authority in milliseconds.
    pub conflict_ms: u64,

    /// Total elapsed time in milliseconds.
    pub total_ms: u64,

    /// Lines of code scanned (if available).
    pub lines_of_code: Option<usize>,
}

impl ScanResult {
    /// Create a stub result for initial CLI testing.
    pub fn stub(path: &Path, format: &str) -> Self {
        Self {
            project: path.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
            scan_id: "stub-scan-id".to_string(),
            files_scanned: 0,
            claims_extracted: 0,
            conflicts: vec![],
            drifts: vec![],
            format: format.to_string(),
            debug: false,
            observations_recorded: 0,
            timing: None,
            claims: None,
            deprecated_usages: vec![],
        }
    }

    /// Check if any BLOCK-level conflicts exist.
    pub fn has_blocks(&self) -> bool {
        self.conflicts.iter().any(|c| c.verdict == Verdict::Block)
    }

    /// Check if any FLAG-level conflicts exist.
    pub fn has_flags(&self) -> bool {
        self.conflicts.iter().any(|c| c.verdict == Verdict::Flag)
    }

    /// Check if any drifts were detected.
    #[must_use]
    pub fn has_drifts(&self) -> bool {
        !self.drifts.is_empty()
    }

    /// Count of drifts detected.
    #[must_use]
    pub fn drift_count(&self) -> usize {
        self.drifts.len()
    }

    /// Check if any deprecated pattern usages were detected.
    #[must_use]
    pub fn has_deprecated_usages(&self) -> bool {
        !self.deprecated_usages.is_empty()
    }

    /// Count of deprecated pattern usages.
    #[must_use]
    pub fn deprecated_usage_count(&self) -> usize {
        self.deprecated_usages.len()
    }

    /// Count of overdue (past sunset) deprecated usages.
    #[must_use]
    pub fn overdue_deprecated_count(&self) -> usize {
        self.deprecated_usages.iter().filter(|u| u.is_past_sunset()).count()
    }

    /// Count conflicts by verdict.
    pub fn count_by_verdict(&self, verdict: Verdict) -> usize {
        self.conflicts.iter().filter(|c| c.verdict == verdict).count()
    }
}

/// Result of conflict detection for a single claim.
#[derive(Debug, Clone)]
pub struct ConflictResult {
    /// The extracted claim.
    pub claim: ExtractedClaim,

    /// Sources that conflict with this claim.
    pub conflicts: Vec<ConflictingSource>,

    /// Computed conflict score (0.0 to 1.0).
    pub conflict_score: f32,

    /// The verdict based on thresholds.
    pub verdict: Verdict,

    /// Whether this conflict has been acknowledged.
    pub acknowledged: Option<AcknowledgmentInfo>,

    /// Debug trace explaining why this conflict was raised.
    pub trace: Option<ConflictTrace>,
}

impl fmt::Display for ConflictResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let verdict_str = match self.verdict {
            Verdict::Block => "BLOCK",
            Verdict::Flag => "FLAG",
            Verdict::Pass => "PASS",
            Verdict::Ack => "ACK",
            Verdict::Drift => "DRIFT",
        };

        writeln!(f, "  {}    {}", verdict_str, self.claim.concept_path)?;
        writeln!(
            f,
            "         Your code:  {} ({}: L{})",
            self.claim.description, self.claim.file, self.claim.line
        )?;

        for source in &self.conflicts {
            writeln!(
                f,
                "         {:?}:   {:?} (Tier {})",
                source.source_class,
                source.value,
                source.source_class.tier()
            )?;
        }

        writeln!(f, "         Conflict:   {:.2}", self.conflict_score)?;

        if let Some(ack) = &self.acknowledged {
            writeln!(f, "         Acknowledged: {} by {}", ack.timestamp, ack.by)?;
            writeln!(f, "         Reason: \"{}\"", ack.reason)?;
        }

        // Display trace if present
        if let Some(trace) = &self.trace {
            writeln!(f, "         --- Debug Trace ---")?;
            writeln!(f, "         Code claim:     {}", trace.code_claim)?;
            writeln!(f, "         Authority match: {}", trace.authority_match)?;
            writeln!(f, "         Authority tier:  {}", trace.authority_tier)?;
            writeln!(f, "         Resolution:      {}", trace.resolution)?;
        }

        Ok(())
    }
}

/// Debug trace explaining the conflict resolution logic.
#[derive(Debug, Clone)]
pub struct ConflictTrace {
    /// The code claim that triggered the conflict.
    pub code_claim: String,

    /// The authoritative assertion that matched.
    pub authority_match: String,

    /// The tier of the authoritative source.
    pub authority_tier: String,

    /// The conflict score before thresholds.
    pub conflict_score: f32,

    /// The resolution (e.g., "BLOCK (Authority outweighs Code)").
    pub resolution: String,
}

impl ConflictTrace {
    /// Create a new conflict trace.
    pub fn new(
        code_claim: &str,
        authority_match: &str,
        source_class: SourceClass,
        conflict_score: f32,
        verdict: Verdict,
    ) -> Self {
        let tier_name = match source_class.tier() {
            0 => "Tier 0 (Regulatory)",
            1 => "Tier 1 (Clinical)",
            2 => "Tier 2 (Observational)",
            3 => "Tier 3 (Expert)",
            _ => "Unknown",
        };

        let resolution = match verdict {
            Verdict::Block => {
                format!("BLOCK (Authority outweighs Code, score {:.2})", conflict_score)
            }
            Verdict::Flag => format!("FLAG (Review recommended, score {:.2})", conflict_score),
            Verdict::Pass => format!("PASS (Below threshold, score {:.2})", conflict_score),
            Verdict::Ack => "ACK (Previously acknowledged)".to_string(),
            Verdict::Drift => "DRIFT (Value changed from prior observation)".to_string(),
        };

        Self {
            code_claim: code_claim.to_string(),
            authority_match: authority_match.to_string(),
            authority_tier: tier_name.to_string(),
            conflict_score,
            resolution,
        }
    }
}

/// Information about an acknowledgment.
#[derive(Debug, Clone)]
pub struct AcknowledgmentInfo {
    /// When the acknowledgment was made.
    pub timestamp: String,

    /// Who made the acknowledgment.
    pub by: String,

    /// The reason given.
    pub reason: String,

    /// When this acknowledgment expires (ISO 8601 date), if ever.
    ///
    /// `None` indicates a permanent acknowledgment.
    pub expires_at: Option<String>,

    /// Whether this acknowledgment has expired.
    ///
    /// When `true`, the conflict should resurface as BLOCK/FLAG.
    /// The acknowledgment is preserved for audit trail per patent claim 25.
    pub expired: bool,
}

/// Result of drift detection for a single claim.
///
/// A drift occurs when a claim's current value differs from a previously
/// recorded observation. This helps detect unintentional configuration changes.
#[derive(Debug, Clone)]
pub struct DriftResult {
    /// The current claim that differs from the prior observation.
    pub claim: ExtractedClaim,

    /// The prior observation that was recorded.
    pub prior: PriorObservation,

    /// Verdict is always Drift for drift results.
    pub verdict: Verdict,
}

impl fmt::Display for DriftResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "  DRIFT   {}", self.claim.concept_path)?;
        writeln!(
            f,
            "         Current: {:?} ({}:{})",
            self.claim.value, self.claim.file, self.claim.line
        )?;
        writeln!(
            f,
            "         Prior:   {:?} ({}:{}, recorded {})",
            self.prior.value, self.prior.file, self.prior.line, self.prior.timestamp
        )?;
        writeln!(f, "         Action:  Review if this change was intentional")?;
        Ok(())
    }
}

/// A prior observation that the current claim drifted from.
#[derive(Debug, Clone)]
pub struct PriorObservation {
    /// The previously observed value.
    pub value: ObjectValue,

    /// Timestamp when this observation was recorded (Unix epoch seconds).
    pub timestamp: u64,

    /// Source file where the observation was originally found.
    pub file: String,

    /// Line number in the source file.
    pub line: usize,
}

/// Result of a deprecated pattern usage.
///
/// Generated when a deprecated pattern matches during scan. The pattern
/// continues to FLAG (not BLOCK) but includes migration guidance.
#[derive(Debug, Clone)]
pub struct DeprecatedUsageResult {
    /// ID of the deprecated pattern.
    pub pattern_id: uuid::Uuid,

    /// Human-readable pattern name.
    pub pattern_name: String,

    /// File where the pattern was used.
    pub file_path: String,

    /// Line number in the file.
    pub line: usize,

    /// Reason for deprecation.
    pub reason: String,

    /// Name of the pattern that supersedes this one (if any).
    pub superseded_by: Option<String>,

    /// URL or text with migration guidance.
    pub migration_guide: Option<String>,

    /// Days until sunset (negative if past due).
    pub days_until_sunset: Option<i64>,
}

impl DeprecatedUsageResult {
    /// Check if this usage is past its sunset date.
    pub fn is_past_sunset(&self) -> bool {
        self.days_until_sunset.map(|d| d < 0).unwrap_or(false)
    }

    /// Get severity based on sunset date.
    pub fn severity(&self) -> &'static str {
        match self.days_until_sunset {
            Some(d) if d < 0 => "OVERDUE",
            Some(d) if d < 30 => "URGENT",
            Some(_) => "WARNING",
            None => "INFO",
        }
    }
}

impl fmt::Display for DeprecatedUsageResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let severity = self.severity();
        writeln!(f, "  {} (deprecated)  {}", severity, self.pattern_name)?;
        writeln!(f, "         Location: {}:{}", self.file_path, self.line)?;
        writeln!(f, "         Reason:   {}", self.reason)?;

        if let Some(ref replacement) = self.superseded_by {
            writeln!(f, "         Replace:  Use '{}'", replacement)?;
        }

        if let Some(ref guide) = self.migration_guide {
            writeln!(f, "         Guide:    {}", guide)?;
        }

        if let Some(days) = self.days_until_sunset {
            if days < 0 {
                writeln!(f, "         Sunset:   OVERDUE by {} days", -days)?;
            } else {
                writeln!(f, "         Sunset:   {} days remaining", days)?;
            }
        }

        Ok(())
    }
}

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

    #[test]
    fn test_scan_result_has_blocks() {
        let result = ScanResult {
            project: "test".to_string(),
            scan_id: "id".to_string(),
            files_scanned: 0,
            claims_extracted: 0,
            conflicts: vec![],
            drifts: vec![],
            format: "table".to_string(),
            debug: false,
            observations_recorded: 0,
            timing: None,
            claims: None,
            deprecated_usages: vec![],
        };

        assert!(!result.has_blocks());
        assert!(!result.has_flags());
        assert!(!result.has_drifts());
        assert_eq!(result.drift_count(), 0);
        assert!(!result.has_deprecated_usages());
        assert_eq!(result.deprecated_usage_count(), 0);
    }
}
