//! Markdown output format for documentation and PR comments.
//!
//! Produces a full markdown document with summary table,
//! detailed conflict sections, and action items.

use super::{extract_leaf_concept, object_value_display, verdict_label, ReportFormatter};
use crate::types::{ScanResult, Verdict};

/// Markdown report formatter.
pub struct MarkdownReport;

impl ReportFormatter for MarkdownReport {
    fn format(&self, result: &ScanResult) -> String {
        let mut out = String::new();

        // Title
        out.push_str(&format!("# Aphoria Scan: {}\n\n", result.project));

        // Summary
        let drift_info = if result.has_drifts() {
            format!(" | **{}** drifts", result.drift_count())
        } else {
            String::new()
        };
        out.push_str(&format!(
            "**{}** files scanned | **{}** claims extracted | **{}** conflicts{}\n\n",
            result.files_scanned,
            result.claims_extracted,
            result.conflicts.len(),
            drift_info
        ));

        if result.conflicts.is_empty() && result.drifts.is_empty() {
            out.push_str("No conflicts or drifts found.\n");

            // Still show claims if requested, even with no conflicts
            if let Some(claims) = &result.claims {
                out.push_str("\n## Extracted Claims\n\n");

                if claims.is_empty() {
                    out.push_str("No claims extracted.\n\n");
                } else {
                    out.push_str("| Concept | Value | File | Line | Confidence |\n");
                    out.push_str("|---------|-------|------|------|------------|\n");

                    for claim in claims {
                        out.push_str(&format!(
                            "| `{}` | `{}` | `{}` | {} | {:.0}% |\n",
                            extract_leaf_concept(&claim.concept_path),
                            object_value_display(&claim.value),
                            claim.file,
                            claim.line,
                            claim.confidence * 100.0,
                        ));
                    }
                    out.push('\n');
                }
            }

            return out;
        }

        // Verdict badges
        let blocks = result.count_by_verdict(Verdict::Block);
        let flags = result.count_by_verdict(Verdict::Flag);
        let drifts = result.drift_count();
        if blocks > 0 {
            out.push_str(&format!("**{blocks} BLOCK** "));
        }
        if flags > 0 {
            out.push_str(&format!("**{flags} FLAG** "));
        }
        if drifts > 0 {
            out.push_str(&format!("**{drifts} DRIFT** "));
        }
        out.push('\n');
        out.push('\n');

        // Summary table
        out.push_str("| Verdict | Concept | Citation | File | Score |\n");
        out.push_str("|---------|---------|----------|------|-------|\n");

        for conflict in &result.conflicts {
            let concept = extract_leaf_concept(&conflict.claim.concept_path);

            // Get RFC/OWASP citation
            let citation = conflict
                .conflicts
                .first()
                .and_then(|s| s.rfc_citation.as_ref())
                .map(|c| c.as_str())
                .unwrap_or("-");

            out.push_str(&format!(
                "| {} | `{}` | {} | `{}:{}` | {:.2} |\n",
                verdict_label(conflict.verdict),
                concept,
                citation,
                conflict.claim.file,
                conflict.claim.line,
                conflict.conflict_score,
            ));
        }
        out.push('\n');

        // Detailed sections for BLOCK and FLAG
        let actionable: Vec<_> = result
            .conflicts
            .iter()
            .filter(|c| c.verdict == Verdict::Block || c.verdict == Verdict::Flag)
            .collect();

        if !actionable.is_empty() {
            out.push_str("## Details\n\n");

            for conflict in actionable {
                out.push_str(&format!(
                    "### {} `{}`\n\n",
                    verdict_label(conflict.verdict),
                    conflict.claim.concept_path
                ));

                out.push_str(&format!(
                    "- **Your code:** {} (`{}:{}`)\n",
                    conflict.claim.description, conflict.claim.file, conflict.claim.line
                ));

                for source in &conflict.conflicts {
                    let citation = source
                        .rfc_citation
                        .as_ref()
                        .map(|c| format!(" [{}]", c))
                        .unwrap_or_default();
                    out.push_str(&format!(
                        "- **{:?}** (Tier {}){}: `{}`\n",
                        source.source_class,
                        source.source_class.tier(),
                        citation,
                        object_value_display(&source.value),
                    ));
                    // Show policy source if this came from a Trust Pack
                    if let Some(policy) = &source.policy_source {
                        let signer = policy.signer_name.as_deref().unwrap_or(&policy.issuer_hex);
                        out.push_str(&format!(
                            "  - *Source: {} v{} ({})*\n",
                            policy.pack_name, policy.pack_version, signer
                        ));
                        if let Some(contact) = &policy.contact {
                            out.push_str(&format!("  - *Contact: {}*\n", contact));
                        }
                    }
                }

                out.push_str(&format!("- **Score:** {:.2}\n", conflict.conflict_score));

                if let Some(ack) = &conflict.acknowledged {
                    if ack.expired {
                        // Expired acknowledgment
                        out.push_str(&format!(
                            "- **Previous acknowledgment expired** {}\n",
                            ack.expires_at.as_deref().unwrap_or("(unknown date)")
                        ));
                        out.push_str(&format!(
                            "- **Prior ack** by {} on {}: \"{}\"\n",
                            ack.by, ack.timestamp, ack.reason
                        ));
                        if conflict.verdict == Verdict::Block {
                            out.push_str(
                                "- **Action:** Fix or run `aphoria ack <path> --reason \"...\"`\n",
                            );
                        } else {
                            out.push_str("- **Action:** Review recommended\n");
                        }
                    } else {
                        // Active acknowledgment
                        out.push_str(&format!(
                            "- **Acknowledged** by {} on {}: \"{}\"\n",
                            ack.by, ack.timestamp, ack.reason
                        ));
                        if let Some(exp) = &ack.expires_at {
                            out.push_str(&format!("- **Expires:** {}\n", exp));
                        }
                    }
                } else if conflict.verdict == Verdict::Block {
                    out.push_str(
                        "- **Action:** Fix or run `aphoria ack <path> --reason \"...\"`\n",
                    );
                } else {
                    out.push_str("- **Action:** Review recommended\n");
                }

                out.push('\n');
            }
        }

        // Drift section
        if !result.drifts.is_empty() {
            out.push_str("## Drift Detected\n\n");
            out.push_str("| Concept | Current | Prior | File |\n");
            out.push_str("|---------|---------|-------|------|\n");

            for drift in &result.drifts {
                out.push_str(&format!(
                    "| `{}` | `{}` | `{}` | `{}:{}` |\n",
                    extract_leaf_concept(&drift.claim.concept_path),
                    object_value_display(&drift.claim.value),
                    object_value_display(&drift.prior.value),
                    drift.claim.file,
                    drift.claim.line,
                ));
            }
            out.push('\n');

            out.push_str("**Action:** Review these changes to ensure they were intentional.\n\n");
        }

        // Deprecated patterns section
        if !result.deprecated_usages.is_empty() {
            out.push_str("## Deprecated Patterns\n\n");
            out.push_str("> ⚠️ **Migration Required:** The following patterns are deprecated and should be updated.\n\n");

            out.push_str("| Pattern | File | Severity | Sunset |\n");
            out.push_str("|---------|------|----------|--------|\n");

            for usage in &result.deprecated_usages {
                let sunset = usage
                    .days_until_sunset
                    .map(|d| if d < 0 { format!("OVERDUE ({}d)", -d) } else { format!("{}d", d) })
                    .unwrap_or_else(|| "-".to_string());

                out.push_str(&format!(
                    "| `{}` | `{}:{}` | {} | {} |\n",
                    usage.pattern_name,
                    usage.file_path,
                    usage.line,
                    usage.severity(),
                    sunset,
                ));
            }
            out.push('\n');

            // Details for each deprecated pattern
            out.push_str("### Migration Details\n\n");
            for usage in &result.deprecated_usages {
                out.push_str(&format!("#### `{}`\n\n", usage.pattern_name));
                out.push_str(&format!("- **Reason:** {}\n", usage.reason));
                out.push_str(&format!("- **Location:** `{}:{}`\n", usage.file_path, usage.line));

                if let Some(ref replacement) = usage.superseded_by {
                    out.push_str(&format!("- **Replacement:** `{}`\n", replacement));
                }

                if let Some(ref guide) = usage.migration_guide {
                    out.push_str(&format!("- **Migration Guide:** {}\n", guide));
                }

                out.push('\n');
            }
        }

        // Extracted Claims section
        if let Some(claims) = &result.claims {
            out.push_str("## Extracted Claims\n\n");

            if claims.is_empty() {
                out.push_str("No claims extracted.\n\n");
            } else {
                out.push_str("| Concept | Value | File | Line | Confidence |\n");
                out.push_str("|---------|-------|------|------|------------|\n");

                for claim in claims {
                    out.push_str(&format!(
                        "| `{}` | `{}` | `{}` | {} | {:.0}% |\n",
                        extract_leaf_concept(&claim.concept_path),
                        object_value_display(&claim.value),
                        claim.file,
                        claim.line,
                        claim.confidence * 100.0,
                    ));
                }
                out.push('\n');
            }
        }

        out
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{ConflictResult, ConflictingSource, ExtractedClaim};
    use stemedb_core::types::{ObjectValue, SourceClass};

    #[test]
    fn test_markdown_with_conflicts() {
        let formatter = MarkdownReport;
        let result = ScanResult {
            project: "testproject".to_string(),
            scan_id: "scan-md".to_string(),
            files_scanned: 20,
            claims_extracted: 4,
            conflicts: vec![ConflictResult {
                claim: ExtractedClaim {
                    concept_path: "code://rust/test/cors/allow_origin".to_string(),
                    predicate: "config_value".to_string(),
                    value: ObjectValue::Text("*".to_string()),
                    file: "src/server.rs".to_string(),
                    line: 55,
                    matched_text: "allow_origin(\"*\")".to_string(),
                    confidence: 1.0,
                    description: "CORS wildcard allow-origin".to_string(),
                },
                conflicts: vec![ConflictingSource {
                    path: "owasp://cors/allow_origin".to_string(),
                    source_class: SourceClass::Clinical,
                    value: ObjectValue::Text("explicit_list".to_string()),
                    confidence: 1.0,
                    rfc_citation: Some("OWASP A05:2021".to_string()),
                    policy_source: None,
                }],
                conflict_score: 0.77,
                verdict: Verdict::Block,
                acknowledged: None,
                trace: None,
            }],
            drifts: vec![],
            format: "markdown".to_string(),
            debug: false,
            observations_recorded: 0,
            timing: None,
            claims: None,
            deprecated_usages: vec![],
        };

        let output = formatter.format(&result);

        assert!(output.contains("# Aphoria Scan: testproject"));
        assert!(output.contains("| BLOCK |"));
        assert!(output.contains("## Details"));
        assert!(output.contains("CORS wildcard"));
        assert!(output.contains("`aphoria ack"));
    }

    #[test]
    fn test_markdown_empty() {
        let formatter = MarkdownReport;
        let result = ScanResult::stub(&std::path::PathBuf::from("empty"), "markdown");
        let output = formatter.format(&result);

        assert!(output.contains("No conflicts or drifts found"));
    }
}
