//! Handler for `mcptest ledger` (diff against a baseline, emit from an envelope).

use std::fs;
use std::path::Path;

use anyhow::{Context, Result};
use mcptest_core::ledger;

use crate::cli::args::{LedgerArgs, LedgerCommand, LedgerDiffArgs, LedgerEmitArgs};

/// Dispatch the `mcptest ledger` subcommand.
pub(crate) fn ledger_command(args: LedgerArgs) -> Result<i32> {
    match args.command {
        LedgerCommand::Diff(a) => diff(a),
        LedgerCommand::Emit(a) => emit(a),
    }
}

/// Compare an actual ledger against a baseline and gate on the diff size.
fn diff(args: LedgerDiffArgs) -> Result<i32> {
    let baseline = read_ledger(&args.baseline)?;
    let actual = read_ledger(&args.actual)?;
    let d = ledger::diff(&baseline, &actual);
    if d.is_clean() {
        println!(
            "ledger diff: clean ({} tool call(s) matched the baseline)",
            ledger::tool_calls(&actual).len()
        );
        return Ok(0);
    }
    for r in &d.removed {
        println!("  - removed  {r}");
    }
    for a in &d.added {
        println!("  + added    {a}");
    }
    for p in &d.param_changed {
        println!("  ~ params   {p}");
    }
    let size = d.size();
    if size > args.max_diff {
        eprintln!(
            "ledger diff: {size} divergence(s) exceed --max-diff {}",
            args.max_diff
        );
        Ok(1)
    } else {
        println!(
            "ledger diff: {size} divergence(s) within --max-diff {}",
            args.max_diff
        );
        Ok(0)
    }
}

/// Convert a saved agent envelope JSON into a ledger NDJSON file (or stdout).
fn emit(args: LedgerEmitArgs) -> Result<i32> {
    let text = fs::read_to_string(&args.input)
        .with_context(|| format!("could not read {}", args.input.display()))?;
    let envelope: serde_json::Value = serde_json::from_str(&text)
        .with_context(|| format!("could not parse JSON at {}", args.input.display()))?;
    let entries =
        ledger::entries_from_envelope(&envelope, &args.session_id, ledger::HeaderMeta::default());
    let ndjson = ledger::to_ndjson(&entries);
    let force = args.force;
    match args.output {
        Some(path) => {
            super::overwrite::ensure_overwritable(&path, force)?;
            fs::write(&path, ndjson)
                .with_context(|| format!("could not write {}", path.display()))?;
        }
        None => print!("{ndjson}"),
    }
    Ok(0)
}

fn read_ledger(path: &Path) -> Result<Vec<ledger::LedgerEntry>> {
    let text =
        fs::read_to_string(path).with_context(|| format!("could not read {}", path.display()))?;
    ledger::from_ndjson(&text).map_err(|e| anyhow::anyhow!("{e}"))
}
