//! Report generation for evaluation results.
//!
//! Supports multiple output formats:
//! - Table (default, for terminal)
//! - JSON (for programmatic consumption)
//! - Markdown (for documentation)

use comfy_table::{Cell, Color, Table};
use serde::Serialize;

use super::harness::{EvalResult, EvalVerdict};
use super::metrics::FixtureStatus;

/// Output format for reports.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReportFormat {
    /// Terminal table format.
    Table,
    /// JSON format.
    Json,
    /// Markdown format.
    Markdown,
}

/// Report generator.
pub struct Report<'a> {
    result: &'a EvalResult,
}

impl<'a> Report<'a> {
    /// Create a new report from evaluation result.
    pub fn new(result: &'a EvalResult) -> Self {
        Self { result }
    }

    /// Render the report in the specified format.
    pub fn render(&self, format: ReportFormat) -> String {
        match format {
            ReportFormat::Table => self.render_table(),
            ReportFormat::Json => self.render_json(),
            ReportFormat::Markdown => self.render_markdown(),
        }
    }

    /// Render as terminal table.
    fn render_table(&self) -> String {
        let mut output = String::new();

        // Header
        output.push_str(&format!("\n{}\n", "═".repeat(70)));
        output.push_str("  LLM Prompt Evaluation Report\n");
        output.push_str(&format!("{}\n\n", "═".repeat(70)));

        // Run info
        output.push_str(&format!("  Run ID:   {}\n", self.result.run_id));
        output.push_str(&format!("  Mode:     {}\n", self.result.mode));
        output.push_str(&format!("  Prompt:   {}\n", self.result.prompt_version));
        output.push_str(&format!("  Model:    {}\n", self.result.model));
        output.push_str(&format!("  Started:  {}\n\n", self.result.started_at));

        // Summary metrics
        output.push_str("Summary\n");
        output.push_str(&format!("{}\n", "─".repeat(50)));

        let mut summary_table = Table::new();
        summary_table.set_header(vec!["Metric", "Value", "Status"]);

        // Precision
        let precision_status = self.metric_status(
            self.result.metrics.precision,
            self.result.baseline_comparison.as_ref().map(|b| b.baseline.precision),
        );
        summary_table.add_row(vec![
            Cell::new("Precision"),
            Cell::new(format!("{:.2}", self.result.metrics.precision)),
            precision_status,
        ]);

        // Recall
        let recall_status = self.metric_status(
            self.result.metrics.recall,
            self.result.baseline_comparison.as_ref().map(|b| b.baseline.recall),
        );
        summary_table.add_row(vec![
            Cell::new("Recall"),
            Cell::new(format!("{:.2}", self.result.metrics.recall)),
            recall_status,
        ]);

        // F1
        let f1_status = self.metric_status(
            self.result.metrics.f1,
            self.result.baseline_comparison.as_ref().map(|b| b.baseline.f1),
        );
        summary_table.add_row(vec![
            Cell::new("F1"),
            Cell::new(format!("{:.2}", self.result.metrics.f1)),
            f1_status,
        ]);

        // Parse success rate
        summary_table.add_row(vec![
            Cell::new("Parse Rate"),
            Cell::new(format!("{:.0}%", self.result.metrics.parse_success_rate * 100.0)),
            Cell::new(""),
        ]);

        output.push_str(&format!("{}\n\n", summary_table));

        // Baseline comparison
        if let Some(comparison) = &self.result.baseline_comparison {
            output.push_str("Baseline Comparison\n");
            output.push_str(&format!("{}\n", "─".repeat(50)));

            let mut baseline_table = Table::new();
            baseline_table.set_header(vec!["Metric", "Current", "Baseline", "Delta"]);

            baseline_table.add_row(vec![
                Cell::new("Precision"),
                Cell::new(format!("{:.2}", comparison.current.precision)),
                Cell::new(format!("{:.2}", comparison.baseline.precision)),
                self.delta_cell(comparison.precision_delta),
            ]);

            baseline_table.add_row(vec![
                Cell::new("Recall"),
                Cell::new(format!("{:.2}", comparison.current.recall)),
                Cell::new(format!("{:.2}", comparison.baseline.recall)),
                self.delta_cell(comparison.recall_delta),
            ]);

            baseline_table.add_row(vec![
                Cell::new("F1"),
                Cell::new(format!("{:.2}", comparison.current.f1)),
                Cell::new(format!("{:.2}", comparison.baseline.f1)),
                self.delta_cell(comparison.f1_delta),
            ]);

            output.push_str(&format!("{}\n\n", baseline_table));
        }

        // Verdict
        let verdict_display = match self.result.verdict {
            EvalVerdict::Pass => "\x1b[32mPASS\x1b[0m", // Green
            EvalVerdict::Regression => "\x1b[31mREGRESSION\x1b[0m", // Red
            EvalVerdict::Review => "\x1b[33mREVIEW\x1b[0m", // Yellow
            EvalVerdict::Error => "\x1b[31mERROR\x1b[0m", // Red
        };
        output.push_str(&format!("Verdict: {}\n\n", verdict_display));

        // Category breakdown
        if !self.result.metrics.by_category.is_empty() {
            output.push_str("Category Breakdown\n");
            output.push_str(&format!("{}\n", "─".repeat(50)));

            let mut cat_table = Table::new();
            cat_table.set_header(vec!["Category", "Fixtures", "Passed", "Failed", "P", "R", "F1"]);

            for (category, metrics) in &self.result.metrics.by_category {
                cat_table.add_row(vec![
                    Cell::new(category),
                    Cell::new(metrics.fixtures.to_string()),
                    Cell::new(metrics.passed.to_string()).fg(Color::Green),
                    Cell::new(metrics.failed.to_string()).fg(if metrics.failed > 0 {
                        Color::Red
                    } else {
                        Color::White
                    }),
                    Cell::new(format!("{:.2}", metrics.precision)),
                    Cell::new(format!("{:.2}", metrics.recall)),
                    Cell::new(format!("{:.2}", metrics.f1)),
                ]);
            }

            output.push_str(&format!("{}\n\n", cat_table));
        }

        // Failed fixtures
        let failed: Vec<_> = self
            .result
            .fixture_results
            .iter()
            .filter(|f| f.status == FixtureStatus::Failed)
            .collect();

        if !failed.is_empty() {
            output.push_str(&format!("Failed Fixtures ({})\n", failed.len()));
            output.push_str(&format!("{}\n", "─".repeat(50)));

            for fixture in failed.iter().take(10) {
                output.push_str(&format!("\n  {} ({})\n", fixture.fixture_id, fixture.category));

                if !fixture.unmatched_expectations.is_empty() {
                    output.push_str("    Unmatched expectations:\n");
                    for exp in &fixture.unmatched_expectations {
                        output.push_str(&format!(
                            "      - {}/{} = {:?}\n",
                            exp.subject, exp.predicate, exp.expected_value
                        ));
                        if let Some(rationale) = &exp.rationale {
                            output.push_str(&format!("        Rationale: {}\n", rationale));
                        }
                    }
                }

                if !fixture.violation_details.is_empty() {
                    output.push_str("    Violations:\n");
                    for viol in &fixture.violation_details {
                        output.push_str(&format!(
                            "      - {}/{} found: {}\n",
                            viol.subject, viol.predicate, viol.found_value
                        ));
                    }
                }
            }

            if failed.len() > 10 {
                output.push_str(&format!("\n  ... and {} more\n", failed.len() - 10));
            }
            output.push('\n');
        }

        // Cost summary
        output.push_str("Cost Summary\n");
        output.push_str(&format!("{}\n", "─".repeat(50)));
        output.push_str(&format!("  Tokens: {}\n", self.result.metrics.total_tokens));
        output.push_str(&format!("  Cost:   ${:.4}\n", self.result.metrics.estimated_cost_usd));
        output.push_str(&format!("  Latency (avg): {:.0}ms\n", self.result.metrics.avg_latency_ms));

        output
    }

    /// Render as JSON.
    fn render_json(&self) -> String {
        #[derive(Serialize)]
        struct JsonReport<'a> {
            run_id: &'a str,
            started_at: &'a str,
            completed_at: &'a str,
            mode: &'a str,
            prompt_version: &'a str,
            model: &'a str,
            verdict: String,
            metrics: MetricsSummary,
            baseline_comparison: Option<BaselineComparisonSummary>,
        }

        #[derive(Serialize)]
        struct MetricsSummary {
            precision: f64,
            recall: f64,
            f1: f64,
            total_fixtures: usize,
            passed: usize,
            failed: usize,
            errored: usize,
            total_tokens: u64,
            estimated_cost_usd: f64,
        }

        #[derive(Serialize)]
        struct BaselineComparisonSummary {
            precision_delta: f64,
            recall_delta: f64,
            f1_delta: f64,
            has_regression: bool,
        }

        let report = JsonReport {
            run_id: &self.result.run_id.to_string(),
            started_at: &self.result.started_at,
            completed_at: &self.result.completed_at,
            mode: &self.result.mode,
            prompt_version: &self.result.prompt_version,
            model: &self.result.model,
            verdict: format!("{}", self.result.verdict),
            metrics: MetricsSummary {
                precision: self.result.metrics.precision,
                recall: self.result.metrics.recall,
                f1: self.result.metrics.f1,
                total_fixtures: self.result.metrics.total_fixtures,
                passed: self.result.metrics.passed,
                failed: self.result.metrics.failed,
                errored: self.result.metrics.errored,
                total_tokens: self.result.metrics.total_tokens,
                estimated_cost_usd: self.result.metrics.estimated_cost_usd,
            },
            baseline_comparison: self.result.baseline_comparison.as_ref().map(|b| {
                BaselineComparisonSummary {
                    precision_delta: b.precision_delta,
                    recall_delta: b.recall_delta,
                    f1_delta: b.f1_delta,
                    has_regression: b.has_regression,
                }
            }),
        };

        serde_json::to_string_pretty(&report).unwrap_or_else(|_| "{}".to_string())
    }

    /// Render as Markdown.
    fn render_markdown(&self) -> String {
        let mut output = String::new();

        output.push_str("# LLM Prompt Evaluation Report\n\n");

        // Run info
        output.push_str(&format!("**Run ID:** {}\n", self.result.run_id));
        output.push_str(&format!("**Date:** {}\n", self.result.started_at));
        output.push_str(&format!("**Prompt:** {}\n", self.result.prompt_version));
        output.push_str(&format!("**Model:** {}\n\n", self.result.model));

        // Summary
        output.push_str("## Summary\n\n");
        output.push_str("| Metric | Value |\n");
        output.push_str("|--------|-------|\n");
        output.push_str(&format!("| Precision | {:.2} |\n", self.result.metrics.precision));
        output.push_str(&format!("| Recall | {:.2} |\n", self.result.metrics.recall));
        output.push_str(&format!("| F1 | {:.2} |\n", self.result.metrics.f1));
        output.push_str(&format!("| Total Fixtures | {} |\n", self.result.metrics.total_fixtures));
        output.push_str(&format!("| Passed | {} |\n", self.result.metrics.passed));
        output.push_str(&format!("| Failed | {} |\n\n", self.result.metrics.failed));

        // Verdict
        let verdict_emoji = match self.result.verdict {
            EvalVerdict::Pass => "✅",
            EvalVerdict::Regression => "❌",
            EvalVerdict::Review => "⚠️",
            EvalVerdict::Error => "🚨",
        };
        output.push_str(&format!("**Verdict:** {} {}\n\n", verdict_emoji, self.result.verdict));

        // Baseline comparison
        if let Some(comparison) = &self.result.baseline_comparison {
            output.push_str("## Baseline Comparison\n\n");
            output.push_str("| Metric | Current | Baseline | Delta |\n");
            output.push_str("|--------|---------|----------|-------|\n");
            output.push_str(&format!(
                "| Precision | {:.2} | {:.2} | {:+.2} |\n",
                comparison.current.precision,
                comparison.baseline.precision,
                comparison.precision_delta
            ));
            output.push_str(&format!(
                "| Recall | {:.2} | {:.2} | {:+.2} |\n",
                comparison.current.recall, comparison.baseline.recall, comparison.recall_delta
            ));
            output.push_str(&format!(
                "| F1 | {:.2} | {:.2} | {:+.2} |\n\n",
                comparison.current.f1, comparison.baseline.f1, comparison.f1_delta
            ));
        }

        // Category breakdown
        if !self.result.metrics.by_category.is_empty() {
            output.push_str("## Category Breakdown\n\n");
            output.push_str("| Category | Fixtures | Passed | Failed | Precision | Recall |\n");
            output.push_str("|----------|----------|--------|--------|-----------|--------|\n");

            for (category, metrics) in &self.result.metrics.by_category {
                output.push_str(&format!(
                    "| {} | {} | {} | {} | {:.2} | {:.2} |\n",
                    category,
                    metrics.fixtures,
                    metrics.passed,
                    metrics.failed,
                    metrics.precision,
                    metrics.recall
                ));
            }
            output.push('\n');
        }

        // Cost
        output.push_str("## Cost\n\n");
        output.push_str(&format!("- **Tokens:** {}\n", self.result.metrics.total_tokens));
        output.push_str(&format!(
            "- **Estimated Cost:** ${:.4}\n",
            self.result.metrics.estimated_cost_usd
        ));

        output
    }

    /// Create a colored cell for metric status.
    fn metric_status(&self, current: f64, baseline: Option<f64>) -> Cell {
        match baseline {
            Some(base) => {
                let delta = current - base;
                if delta >= 0.0 {
                    Cell::new("✓").fg(Color::Green)
                } else if delta > -0.05 {
                    Cell::new("~").fg(Color::Yellow)
                } else {
                    Cell::new("✗").fg(Color::Red)
                }
            }
            None => Cell::new("-"),
        }
    }

    /// Create a colored cell for delta.
    fn delta_cell(&self, delta: f64) -> Cell {
        let text = format!("{:+.2}", delta);
        if delta >= 0.0 {
            Cell::new(text).fg(Color::Green)
        } else if delta > -0.05 {
            Cell::new(text).fg(Color::Yellow)
        } else {
            Cell::new(text).fg(Color::Red)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::eval::metrics::Metrics;
    use uuid::Uuid;

    fn make_test_result() -> EvalResult {
        EvalResult {
            run_id: Uuid::new_v4(),
            started_at: "2026-02-05T10:00:00Z".to_string(),
            completed_at: "2026-02-05T10:01:00Z".to_string(),
            mode: "Mock".to_string(),
            prompt_version: "1.0.0".to_string(),
            model: "gemini-2.0-flash".to_string(),
            metrics: Metrics {
                precision: 0.85,
                recall: 0.78,
                f1: 0.81,
                total_fixtures: 10,
                passed: 8,
                failed: 2,
                errored: 0,
                total_tokens: 10000,
                estimated_cost_usd: 0.01,
                avg_latency_ms: 500.0,
                parse_success_rate: 1.0,
                ..Default::default()
            },
            fixture_results: Vec::new(),
            baseline_comparison: None,
            verdict: EvalVerdict::Review,
        }
    }

    #[test]
    fn test_table_report() {
        let result = make_test_result();
        let report = Report::new(&result);
        let output = report.render(ReportFormat::Table);

        assert!(output.contains("LLM Prompt Evaluation Report"));
        assert!(output.contains("0.85")); // precision
        assert!(output.contains("0.78")); // recall
    }

    #[test]
    fn test_json_report() {
        let result = make_test_result();
        let report = Report::new(&result);
        let output = report.render(ReportFormat::Json);

        assert!(output.contains("\"precision\": 0.85"));
        assert!(output.contains("\"recall\": 0.78"));
        assert!(output.contains("\"verdict\": \"REVIEW\""));
    }

    #[test]
    fn test_markdown_report() {
        let result = make_test_result();
        let report = Report::new(&result);
        let output = report.render(ReportFormat::Markdown);

        assert!(output.contains("# LLM Prompt Evaluation Report"));
        assert!(output.contains("| Precision | 0.85 |"));
        assert!(output.contains("⚠️ REVIEW"));
    }
}
