//! Table output format for terminal display.
//!
//! Uses `comfy-table` for clean, aligned terminal output with
//! a summary header and detailed conflict sections.

use comfy_table::{Cell, CellAlignment, Color, ContentArrangement, Table};

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

/// Table report formatter for terminal output.
pub struct TableReport;

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

        // Header
        output.push_str(&format!("Aphoria Report: {}\n", result.project));
        let drift_info = if result.has_drifts() {
            format!(" | Drifts: {}", result.drift_count())
        } else {
            String::new()
        };
        output.push_str(&format!(
            "Scanned: {} files | Claims: {} | Conflicts: {}{}\n\n",
            result.files_scanned,
            result.claims_extracted,
            result.conflicts.len(),
            drift_info
        ));

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

            // Still show claims if requested, even with no conflicts
            if let Some(claims) = &result.claims {
                if claims.is_empty() {
                    output.push_str("\nExtracted Claims:\n\n");
                    output.push_str("  No claims extracted.\n");
                } else {
                    output.push_str("\nExtracted Claims:\n\n");
                    let mut claims_table = Table::new();
                    claims_table.set_content_arrangement(ContentArrangement::Dynamic);
                    claims_table.set_header(vec![
                        Cell::new("Concept"),
                        Cell::new("Value"),
                        Cell::new("File"),
                        Cell::new("Line").set_alignment(CellAlignment::Right),
                        Cell::new("Confidence").set_alignment(CellAlignment::Right),
                    ]);

                    for claim in claims {
                        claims_table.add_row(vec![
                            Cell::new(extract_leaf_concept(&claim.concept_path)),
                            Cell::new(object_value_display(&claim.value)),
                            Cell::new(&claim.file),
                            Cell::new(claim.line.to_string()).set_alignment(CellAlignment::Right),
                            Cell::new(format!("{:.0}%", claim.confidence * 100.0))
                                .set_alignment(CellAlignment::Right),
                        ]);
                    }

                    output.push_str(&claims_table.to_string());
                    output.push('\n');
                }
            }

            return output;
        }

        // Summary table
        let mut table = Table::new();
        table.set_content_arrangement(ContentArrangement::Dynamic);
        table.set_header(vec![
            Cell::new("Verdict").set_alignment(CellAlignment::Center),
            Cell::new("Concept"),
            Cell::new("Citation"),
            Cell::new("Score").set_alignment(CellAlignment::Right),
            Cell::new("Tier"),
        ]);

        for conflict in &result.conflicts {
            let verdict = verdict_label(conflict.verdict);
            let verdict_cell = match conflict.verdict {
                Verdict::Block => Cell::new(verdict).fg(Color::Red),
                Verdict::Flag => Cell::new(verdict).fg(Color::Yellow),
                Verdict::Ack => Cell::new(verdict).fg(Color::Cyan),
                Verdict::Pass => Cell::new(verdict).fg(Color::Green),
                Verdict::Drift => Cell::new(verdict).fg(Color::Magenta),
            };

            // Extract leaf concept from full path for brevity
            let concept = extract_leaf_concept(&conflict.claim.concept_path);

            let tier_spread = if let Some(source) = conflict.conflicts.first() {
                format!("{}↔3", source.source_class.tier())
            } else {
                "?↔3".to_string()
            };

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

            table.add_row(vec![
                verdict_cell,
                Cell::new(concept),
                Cell::new(citation),
                Cell::new(format!("{:.2}", conflict.conflict_score))
                    .set_alignment(CellAlignment::Right),
                Cell::new(tier_spread).set_alignment(CellAlignment::Center),
            ]);
        }

        output.push_str(&table.to_string());
        output.push('\n');

        // Detail 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() {
            output.push_str("\nDetails:\n\n");
            for conflict in actionable {
                let verdict = verdict_label(conflict.verdict);
                output.push_str(&format!("  {:<6} {}\n", verdict, conflict.claim.concept_path));
                output.push_str(&format!(
                    "         Your code:  {} ({}:{})\n",
                    conflict.claim.description, conflict.claim.file, conflict.claim.line
                ));

                for source in &conflict.conflicts {
                    output.push_str(&format!(
                        "         {:?}:   {:?} (Tier {})\n",
                        source.source_class,
                        source.value,
                        source.source_class.tier()
                    ));
                    // Show policy source if this came from a Trust Pack
                    if let Some(policy) = &source.policy_source {
                        // Display signer name if available, fall back to issuer hex
                        let signer = policy.signer_name.as_deref().unwrap_or(&policy.issuer_hex);
                        output.push_str(&format!(
                            "         Source: {} v{} ({})\n",
                            policy.pack_name, policy.pack_version, signer
                        ));
                        // Display contact info if available
                        if let Some(contact) = &policy.contact {
                            output.push_str(&format!("         Contact: {}\n", contact));
                        }
                    }
                }

                if let Some(ack) = &conflict.acknowledged {
                    if ack.expired {
                        // Expired acknowledgment - show note about expiry
                        output.push_str(&format!(
                            "         Note:       Previous acknowledgment expired {}\n",
                            ack.expires_at.as_deref().unwrap_or("(unknown date)")
                        ));
                        output.push_str(&format!(
                            "         Prior ack:  {} by {}: \"{}\"\n",
                            ack.timestamp, ack.by, ack.reason
                        ));
                        if conflict.verdict == Verdict::Block {
                            output.push_str(
                                "         Action:     Fix or re-acknowledge with: aphoria ack <path> --reason \"...\"\n",
                            );
                        } else {
                            output.push_str("         Action:     Review recommended\n");
                        }
                    } else {
                        // Active acknowledgment
                        output.push_str(&format!(
                            "         Acknowledged: {} by {}: \"{}\"\n",
                            ack.timestamp, ack.by, ack.reason
                        ));
                        if let Some(exp) = &ack.expires_at {
                            output.push_str(&format!("         Expires:    {}\n", exp));
                        }
                    }
                } else if conflict.verdict == Verdict::Block {
                    output.push_str(
                        "         Action:     Fix or acknowledge with: aphoria ack <path> --reason \"...\"\n",
                    );
                } else {
                    output.push_str("         Action:     Review recommended\n");
                }

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

        // Drift section
        if !result.drifts.is_empty() {
            output.push_str("\nDrift Detected:\n\n");
            for drift in &result.drifts {
                output.push_str(&format!(
                    "  DRIFT  {}\n",
                    extract_leaf_concept(&drift.claim.concept_path)
                ));
                output.push_str(&format!(
                    "         Current: {} ({}:{})\n",
                    object_value_display(&drift.claim.value),
                    drift.claim.file,
                    drift.claim.line
                ));
                output.push_str(&format!(
                    "         Prior:   {} ({}:{})\n",
                    object_value_display(&drift.prior.value),
                    drift.prior.file,
                    drift.prior.line
                ));
                output.push_str("         Action:  Verify this change was intentional\n\n");
            }
        }

        // Deprecated patterns section
        if !result.deprecated_usages.is_empty() {
            output.push_str("\nDeprecated Patterns:\n\n");
            for usage in &result.deprecated_usages {
                let severity = usage.severity();
                output
                    .push_str(&format!("  {:<8} {} (deprecated)\n", severity, usage.pattern_name));
                output
                    .push_str(&format!("         Location: {}:{}\n", usage.file_path, usage.line));
                output.push_str(&format!("         Reason:   {}\n", usage.reason));

                if let Some(ref replacement) = usage.superseded_by {
                    output.push_str(&format!("         Replace:  Use '{}'\n", replacement));
                }

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

                if let Some(days) = usage.days_until_sunset {
                    if days < 0 {
                        output.push_str(&format!("         Sunset:   OVERDUE by {} days\n", -days));
                    } else {
                        output.push_str(&format!("         Sunset:   {} days remaining\n", days));
                    }
                }

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

        // Extracted Claims section
        if let Some(claims) = &result.claims {
            if claims.is_empty() {
                output.push_str("\nExtracted Claims:\n\n");
                output.push_str("  No claims extracted.\n\n");
            } else {
                output.push_str("\nExtracted Claims:\n\n");
                let mut claims_table = Table::new();
                claims_table.set_content_arrangement(ContentArrangement::Dynamic);
                claims_table.set_header(vec![
                    Cell::new("Concept"),
                    Cell::new("Value"),
                    Cell::new("File"),
                    Cell::new("Line").set_alignment(CellAlignment::Right),
                    Cell::new("Confidence").set_alignment(CellAlignment::Right),
                ]);

                for claim in claims {
                    claims_table.add_row(vec![
                        Cell::new(extract_leaf_concept(&claim.concept_path)),
                        Cell::new(object_value_display(&claim.value)),
                        Cell::new(&claim.file),
                        Cell::new(claim.line.to_string()).set_alignment(CellAlignment::Right),
                        Cell::new(format!("{:.0}%", claim.confidence * 100.0))
                            .set_alignment(CellAlignment::Right),
                    ]);
                }

                output.push_str(&claims_table.to_string());
                output.push('\n');
            }
        }

        // Footer summary
        let block_count = result.count_by_verdict(Verdict::Block);
        let flag_count = result.count_by_verdict(Verdict::Flag);
        let ack_count = result.count_by_verdict(Verdict::Ack);
        let pass_count = result.count_by_verdict(Verdict::Pass);
        let drift_count = result.drift_count();
        let deprecated_count = result.deprecated_usage_count();

        // Build summary parts, omitting zeros for cleaner output
        let mut parts = Vec::new();
        if block_count > 0 {
            parts.push(format!("{} BLOCK", block_count));
        }
        if flag_count > 0 {
            parts.push(format!("{} FLAG", flag_count));
        }
        if drift_count > 0 {
            parts.push(format!("{} DRIFT", drift_count));
        }
        if deprecated_count > 0 {
            parts.push(format!("{} DEPRECATED", deprecated_count));
        }
        if ack_count > 0 {
            parts.push(format!("{} ACK", ack_count));
        }
        if pass_count > 0 {
            parts.push(format!("{} PASS", pass_count));
        }

        if !parts.is_empty() {
            output.push_str(&parts.join(", "));
            output.push('\n');
        }

        // Show observation count if any were recorded
        if result.observations_recorded > 0 {
            output.push_str(&format!(
                "Recorded {} observations (project memory)\n",
                result.observations_recorded
            ));
        }

        // Show benchmark results if timing is available
        if let Some(timing) = &result.timing {
            output.push_str("\nBenchmark Results:\n");
            output.push_str(&format!("  Files scanned:     {}\n", result.files_scanned));
            if let Some(loc) = timing.lines_of_code {
                output.push_str(&format!("  Lines of code:     {}\n", format_number(loc)));
            }
            output.push_str(&format!("  Claims extracted:  {}\n", result.claims_extracted));
            output.push_str(&format!("  Conflicts found:   {}\n", result.conflicts.len()));
            output.push_str(&format!("  Total time:        {}ms\n", timing.total_ms));
            output.push_str(&format!("    - File discovery:  {}ms\n", timing.walk_ms));
            output.push_str(&format!("    - Extraction:      {}ms\n", timing.extraction_ms));
            output.push_str(&format!("    - Conflict query:  {}ms\n", timing.conflict_ms));
        }

        output
    }
}

/// Format a number with thousands separators for readability.
fn format_number(n: usize) -> String {
    let s = n.to_string();
    let mut result = String::new();
    for (i, c) in s.chars().rev().enumerate() {
        if i > 0 && i % 3 == 0 {
            result.push(',');
        }
        result.push(c);
    }
    result.chars().rev().collect()
}

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

    fn sample_result() -> ScanResult {
        ScanResult {
            project: "testproject".to_string(),
            scan_id: "scan-123".to_string(),
            files_scanned: 42,
            claims_extracted: 5,
            conflicts: vec![ConflictResult {
                claim: ExtractedClaim {
                    concept_path: "code://rust/testproject/tls/cert_verification".to_string(),
                    predicate: "enabled".to_string(),
                    value: ObjectValue::Boolean(false),
                    file: "src/client.rs".to_string(),
                    line: 23,
                    matched_text: "danger_accept_invalid_certs(true)".to_string(),
                    confidence: 1.0,
                    description: "TLS verification disabled".to_string(),
                },
                conflicts: vec![ConflictingSource {
                    path: "rfc://5246/tls/cert_verification".to_string(),
                    source_class: SourceClass::Regulatory,
                    value: ObjectValue::Boolean(true),
                    confidence: 1.0,
                    rfc_citation: Some("RFC 5246".to_string()),
                    policy_source: None,
                }],
                conflict_score: 0.92,
                verdict: Verdict::Block,
                acknowledged: None,
                trace: None,
            }],
            drifts: vec![],
            format: "table".to_string(),
            debug: false,
            observations_recorded: 0,
            timing: None,
            claims: None,
            deprecated_usages: vec![],
        }
    }

    #[test]
    fn test_table_with_conflicts() {
        let formatter = TableReport;
        let output = formatter.format(&sample_result());

        assert!(output.contains("testproject"));
        assert!(output.contains("BLOCK"));
        assert!(output.contains("tls"));
        assert!(output.contains("0.92"));
    }

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

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