//! Core types for Aphoria.
// Skeleton phase: allow unused until scan pipeline is wired up
#![allow(dead_code)]

use std::fmt;
use std::path::{Path, PathBuf};

use stemedb_core::types::{ObjectValue, SourceClass};

/// Arguments for the scan command.
#[derive(Debug, Clone)]
pub struct ScanArgs {
    /// Path to the project root.
    pub path: PathBuf,

    /// Output format (table, json, sarif, markdown).
    pub format: String,

    /// Whether to enable non-zero exit codes on conflicts.
    pub exit_code_enabled: bool,
}

/// Arguments for the acknowledge command.
#[derive(Debug, Clone)]
pub struct AcknowledgeArgs {
    /// The concept path to acknowledge.
    pub concept_path: String,

    /// Reason for acknowledgment.
    pub reason: String,
}

/// 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.
    pub conflicts: Vec<ConflictResult>,

    /// Output format.
    pub format: String,
}

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![],
            format: format.to_string(),
        }
    }

    /// 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)
    }

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

    /// Format the result for display.
    pub fn display(&self) -> String {
        match self.format.as_str() {
            "json" => self.display_json(),
            "sarif" => self.display_sarif(),
            "markdown" => self.display_markdown(),
            _ => self.display_table(),
        }
    }

    fn display_table(&self) -> String {
        let mut output = String::new();

        output.push_str(&format!("Scanning {} ...\n\n", self.project));

        if self.conflicts.is_empty() {
            output.push_str("No conflicts found.\n");
        } else {
            for conflict in &self.conflicts {
                output.push_str(&format!("{}\n\n", conflict));
            }
        }

        output.push_str(&format!(
            "{} files scanned, {} claims extracted, {} conflicts ({} BLOCK, {} FLAG)\n",
            self.files_scanned,
            self.claims_extracted,
            self.conflicts.len(),
            self.count_by_verdict(Verdict::Block),
            self.count_by_verdict(Verdict::Flag),
        ));

        output
    }

    fn display_json(&self) -> String {
        // TODO: Implement JSON output
        serde_json::json!({
            "project": self.project,
            "scan_id": self.scan_id,
            "summary": {
                "files_scanned": self.files_scanned,
                "claims_extracted": self.claims_extracted,
                "conflicts": self.conflicts.len(),
                "blocks": self.count_by_verdict(Verdict::Block),
                "flags": self.count_by_verdict(Verdict::Flag),
            },
            "conflicts": []
        })
        .to_string()
    }

    fn display_sarif(&self) -> String {
        // TODO: Implement SARIF output
        serde_json::json!({
            "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
            "version": "2.1.0",
            "runs": [{
                "tool": {
                    "driver": {
                        "name": "aphoria",
                        "version": env!("CARGO_PKG_VERSION"),
                    }
                },
                "results": []
            }]
        })
        .to_string()
    }

    fn display_markdown(&self) -> String {
        let mut output = String::new();

        output.push_str(&format!("# Aphoria Scan: {}\n\n", self.project));
        output.push_str(&format!(
            "**Summary:** {} files, {} claims, {} conflicts\n\n",
            self.files_scanned,
            self.claims_extracted,
            self.conflicts.len()
        ));

        if self.conflicts.is_empty() {
            output.push_str("No conflicts found.\n");
        } else {
            output.push_str("## Conflicts\n\n");
            for conflict in &self.conflicts {
                output.push_str(&format!("### {}\n\n", conflict.claim.concept_path));
                output.push_str(&format!("- **Verdict:** {:?}\n", conflict.verdict));
                output.push_str(&format!("- **Score:** {:.2}\n", conflict.conflict_score));
                output.push_str(&format!(
                    "- **File:** {}:{}\n\n",
                    conflict.claim.file, conflict.claim.line
                ));
            }
        }

        output
    }
}

/// A claim extracted from source code.
#[derive(Debug, Clone)]
pub struct ExtractedClaim {
    /// The full ConceptPath for this claim.
    pub concept_path: String,

    /// The predicate describing what aspect this claims.
    pub predicate: String,

    /// The extracted value.
    pub value: ObjectValue,

    /// Source file path relative to project root.
    pub file: String,

    /// Line number in the source file (1-indexed).
    pub line: usize,

    /// The matched source text.
    pub matched_text: String,

    /// Confidence of extraction (0.0 to 1.0).
    pub confidence: f32,

    /// Human-readable description.
    pub description: String,
}

/// A source that conflicts with the code claim.
#[derive(Debug, Clone)]
pub struct ConflictingSource {
    /// The concept path of the authoritative source.
    pub path: String,

    /// The source class (tier).
    pub source_class: SourceClass,

    /// The authoritative value.
    pub value: ObjectValue,

    /// Confidence of the authoritative assertion.
    pub confidence: f32,
}

/// 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>,
}

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",
        };

        writeln!(f, "  {}    {}", verdict_str, self.claim.concept_path)?;
        writeln!(
            f,
            "         Your code:  {} ({}:{})",
            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)?;
        }

        Ok(())
    }
}

/// 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,
}

/// Verdict for a conflict.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
    /// Conflict score >= block threshold. Must fix or acknowledge.
    Block,

    /// Conflict score >= flag threshold. Review recommended.
    Flag,

    /// Conflict score below thresholds. No action needed.
    Pass,

    /// Conflict exists but has been acknowledged.
    Ack,
}

/// Detected language of a file.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
    /// Rust source files.
    Rust,
    /// Go source files.
    Go,
    /// Python source files.
    Python,
    /// TypeScript source files.
    TypeScript,
    /// JavaScript source files.
    JavaScript,
    /// YAML configuration files.
    Yaml,
    /// TOML configuration files.
    Toml,
    /// JSON configuration files.
    Json,
    /// Dotenv files.
    Dotenv,
    /// Docker files.
    Docker,
    /// Cargo manifest.
    CargoManifest,
    /// Go module file.
    GoMod,
    /// NPM manifest.
    NpmManifest,
    /// Python manifest.
    PythonManifest,
    /// Unknown language.
    Unknown,
}

impl Language {
    /// Detect language from file extension.
    pub fn from_path(path: &Path) -> Self {
        let file_name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
        let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("");

        // Check specific filenames first
        match file_name {
            "Cargo.toml" => return Language::CargoManifest,
            "go.mod" => return Language::GoMod,
            "package.json" => return Language::NpmManifest,
            "requirements.txt" | "pyproject.toml" => return Language::PythonManifest,
            _ if file_name.starts_with("Dockerfile") => return Language::Docker,
            _ if file_name.starts_with("docker-compose") => return Language::Docker,
            _ if file_name.starts_with(".env") => return Language::Dotenv,
            _ => {}
        }

        // Check extensions
        match extension {
            "rs" => Language::Rust,
            "go" => Language::Go,
            "py" => Language::Python,
            "ts" | "tsx" => Language::TypeScript,
            "js" | "jsx" => Language::JavaScript,
            "yaml" | "yml" => Language::Yaml,
            "toml" => Language::Toml,
            "json" => Language::Json,
            _ => Language::Unknown,
        }
    }
}

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

    #[test]
    fn test_language_detection() {
        assert_eq!(Language::from_path(Path::new("src/main.rs")), Language::Rust);
        assert_eq!(Language::from_path(Path::new("main.go")), Language::Go);
        assert_eq!(Language::from_path(Path::new("app.py")), Language::Python);
        assert_eq!(Language::from_path(Path::new("Cargo.toml")), Language::CargoManifest);
        assert_eq!(Language::from_path(Path::new("go.mod")), Language::GoMod);
        assert_eq!(Language::from_path(Path::new(".env.production")), Language::Dotenv);
        assert_eq!(Language::from_path(Path::new("Dockerfile")), Language::Docker);
    }

    #[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![],
            format: "table".to_string(),
        };

        assert!(!result.has_blocks());
        assert!(!result.has_flags());
    }

    #[test]
    fn test_verdict_equality() {
        assert_eq!(Verdict::Block, Verdict::Block);
        assert_ne!(Verdict::Block, Verdict::Flag);
    }
}
