//! Handlers for the `aphoria eval` subcommands.

use std::path::PathBuf;
use std::process::ExitCode;

use comfy_table::{Cell, Color, Table};
use tracing::info;

use aphoria::eval::fixture::FixtureLoader;
use aphoria::eval::harness::{update_baseline, EvalHarness, EvalMode, EvalRunConfig, EvalVerdict};
use aphoria::eval::report::{Report, ReportFormat};
use aphoria::AphoriaConfig;

use crate::cli::EvalCommands;

/// Handle eval subcommands.
pub async fn handle_eval_command(command: EvalCommands, config: &AphoriaConfig) -> ExitCode {
    match command {
        EvalCommands::Run {
            fixtures,
            categories,
            max_fixtures,
            mode,
            fail_on_regression,
            threshold,
            save_observations,
            format,
        } => {
            handle_eval_run(
                fixtures,
                categories,
                max_fixtures,
                mode,
                fail_on_regression,
                threshold,
                save_observations,
                format,
                config,
            )
            .await
        }

        EvalCommands::Baseline { fixtures } => handle_eval_baseline(fixtures).await,

        EvalCommands::UpdateBaseline { fixtures, force: _ } => {
            handle_eval_update_baseline(fixtures, config).await
        }

        EvalCommands::ListFixtures { fixtures, category } => {
            handle_eval_list_fixtures(fixtures, category).await
        }

        EvalCommands::ValidateFixtures { fixtures } => {
            handle_eval_validate_fixtures(fixtures).await
        }
    }
}

/// Handle `aphoria eval run`.
#[allow(clippy::too_many_arguments)]
async fn handle_eval_run(
    fixtures_dir: PathBuf,
    categories: Option<String>,
    max_fixtures: Option<usize>,
    mode: String,
    fail_on_regression: bool,
    threshold: f64,
    save_observations: bool,
    format: String,
    config: &AphoriaConfig,
) -> ExitCode {
    // Parse mode
    let eval_mode = match mode.parse::<EvalMode>() {
        Ok(m) => m,
        Err(e) => {
            eprintln!("Error: {}", e);
            return ExitCode::FAILURE;
        }
    };

    // Parse categories
    let categories_vec = categories.map(|c| c.split(',').map(|s| s.trim().to_string()).collect());

    // Build config
    let run_config = EvalRunConfig {
        fixtures_dir,
        categories: categories_vec,
        max_fixtures,
        mode: eval_mode,
        baseline: None,
        save_observations,
        max_concurrent: config.eval.max_concurrent,
        regression_threshold: threshold,
        model: config.llm.model.clone(),
        prompt_version: aphoria::eval::harness::PROMPT_VERSION.to_string(),
    };

    // Create harness and run
    let harness = match EvalHarness::new(run_config) {
        Ok(h) => h,
        Err(e) => {
            eprintln!("Failed to create evaluation harness: {}", e);
            return ExitCode::FAILURE;
        }
    };

    let result = match harness.run() {
        Ok(r) => r,
        Err(e) => {
            eprintln!("Evaluation failed: {}", e);
            return ExitCode::FAILURE;
        }
    };

    // Parse output format
    let report_format = match format.as_str() {
        "json" => ReportFormat::Json,
        "markdown" | "md" => ReportFormat::Markdown,
        _ => ReportFormat::Table,
    };

    // Generate and print report
    let report = Report::new(&result);
    println!("{}", report.render(report_format));

    // Determine exit code
    if fail_on_regression && result.verdict == EvalVerdict::Regression {
        ExitCode::FAILURE
    } else {
        ExitCode::SUCCESS
    }
}

/// Handle `aphoria eval baseline`.
async fn handle_eval_baseline(fixtures_dir: PathBuf) -> ExitCode {
    let loader = FixtureLoader::new(&fixtures_dir);

    let manifest = match loader.load_manifest() {
        Ok(m) => m,
        Err(e) => {
            eprintln!("Failed to load manifest: {}", e);
            return ExitCode::FAILURE;
        }
    };

    match &manifest.baseline {
        Some(baseline) => {
            let mut table = Table::new();
            table.set_header(vec!["Metric", "Value"]);
            table.add_row(vec![
                Cell::new("Precision"),
                Cell::new(format!("{:.2}", baseline.precision)),
            ]);
            table.add_row(vec![Cell::new("Recall"), Cell::new(format!("{:.2}", baseline.recall))]);
            table.add_row(vec![Cell::new("F1"), Cell::new(format!("{:.2}", baseline.f1))]);
            table.add_row(vec![
                Cell::new("Total Fixtures"),
                Cell::new(baseline.total_fixtures.to_string()),
            ]);
            table.add_row(vec![Cell::new("Prompt Version"), Cell::new(&baseline.prompt_version)]);
            table.add_row(vec![Cell::new("Model"), Cell::new(&baseline.model)]);
            table.add_row(vec![Cell::new("Measured At"), Cell::new(&baseline.measured_at)]);

            println!("Current Baseline\n");
            println!("{table}");
        }
        None => {
            println!("No baseline set. Run `aphoria eval update-baseline --force` to create one.");
        }
    }

    ExitCode::SUCCESS
}

/// Handle `aphoria eval update-baseline`.
async fn handle_eval_update_baseline(fixtures_dir: PathBuf, config: &AphoriaConfig) -> ExitCode {
    // First run an evaluation to get current metrics using cached responses
    let run_config = EvalRunConfig {
        fixtures_dir: fixtures_dir.clone(),
        categories: None,
        max_fixtures: None,
        mode: EvalMode::Cached, // Use cached mode to get real metrics from prior LLM runs
        baseline: None,
        save_observations: false,
        max_concurrent: config.eval.max_concurrent,
        regression_threshold: config.eval.regression_threshold,
        model: config.llm.model.clone(),
        prompt_version: aphoria::eval::harness::PROMPT_VERSION.to_string(),
    };

    let harness = match EvalHarness::new(run_config) {
        Ok(h) => h,
        Err(e) => {
            eprintln!("Failed to create evaluation harness: {}", e);
            return ExitCode::FAILURE;
        }
    };

    let result = match harness.run() {
        Ok(r) => r,
        Err(e) => {
            eprintln!("Evaluation failed: {}", e);
            return ExitCode::FAILURE;
        }
    };

    // Update baseline
    if let Err(e) = update_baseline(&fixtures_dir, &result.metrics) {
        eprintln!("Failed to update baseline: {}", e);
        return ExitCode::FAILURE;
    }

    info!(
        precision = %format!("{:.2}", result.metrics.precision),
        recall = %format!("{:.2}", result.metrics.recall),
        f1 = %format!("{:.2}", result.metrics.f1),
        "Baseline updated"
    );

    println!("Baseline updated successfully.");
    println!("  Precision: {:.2}", result.metrics.precision);
    println!("  Recall: {:.2}", result.metrics.recall);
    println!("  F1: {:.2}", result.metrics.f1);

    ExitCode::SUCCESS
}

/// Handle `aphoria eval list-fixtures`.
async fn handle_eval_list_fixtures(fixtures_dir: PathBuf, category: Option<String>) -> ExitCode {
    let loader = FixtureLoader::new(&fixtures_dir);

    let summaries = match loader.list(category.as_deref()) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Failed to list fixtures: {}", e);
            return ExitCode::FAILURE;
        }
    };

    if summaries.is_empty() {
        println!("No fixtures found.");
        return ExitCode::SUCCESS;
    }

    let mut table = Table::new();
    table.set_header(vec!["ID", "Name", "Category", "Language", "Must Contain", "Must Not"]);

    for summary in summaries {
        table.add_row(vec![
            Cell::new(&summary.id),
            Cell::new(&summary.name),
            Cell::new(&summary.category),
            Cell::new(&summary.language),
            Cell::new(summary.must_contain_count.to_string()),
            Cell::new(summary.must_not_contain_count.to_string()),
        ]);
    }

    println!("{table}");

    ExitCode::SUCCESS
}

/// Handle `aphoria eval validate-fixtures`.
async fn handle_eval_validate_fixtures(fixtures_dir: PathBuf) -> ExitCode {
    let loader = FixtureLoader::new(&fixtures_dir);

    let errors = match loader.validate() {
        Ok(e) => e,
        Err(e) => {
            eprintln!("Failed to validate fixtures: {}", e);
            return ExitCode::FAILURE;
        }
    };

    if errors.is_empty() {
        println!("All fixtures are valid.");
        return ExitCode::SUCCESS;
    }

    println!("Validation errors found:\n");

    let mut table = Table::new();
    table.set_header(vec!["Fixture", "Error"]);

    for error in &errors {
        table.add_row(vec![
            Cell::new(&error.fixture_id).fg(Color::Yellow),
            Cell::new(&error.message).fg(Color::Red),
        ]);
    }

    println!("{table}");

    ExitCode::from(errors.len().min(255) as u8)
}
