//! Handler for `mcptest report` plus the upload-format path.

use std::io;

use anyhow::Result;

use crate::cli::args::{ReportArgs, ReportFormat, UploadArgs};
use crate::cli::GlobalArgs;

/// Handle `mcptest report <PATH>`.
///
/// Reads the saved JSON report at `args.input` and re-renders it in the
/// requested format. Returns Ok(1) when the file is missing or malformed so
/// CI scripts can detect a broken artefact.
pub(crate) fn report_command(args: ReportArgs, global: &GlobalArgs) -> Result<i32> {
    use mcptest_core::report::Report;

    use crate::cli::reporters::{annotations_enabled, build_reporter};

    let body = match std::fs::read_to_string(&args.input) {
        Ok(b) => b,
        Err(e) => {
            eprintln!("mcptest report: cannot read {:?}: {e}", args.input);
            return Ok(1);
        }
    };
    let report: Report = match serde_json::from_str(&body) {
        Ok(r) => r,
        Err(e) => {
            eprintln!(
                "mcptest report: {:?} is not a valid mcptest JSON report: {e}",
                args.input
            );
            return Ok(1);
        }
    };
    if matches!(args.format, ReportFormat::Upload) {
        return upload_saved_report(&report, &args.upload);
    }
    let format = args
        .format
        .render_format()
        .expect("upload is the only non-rendering format and is handled above");
    // Resolve color for this sink: a file (`--output`) stays plain; stdout
    // colors per `--color` and the terminal. Machine formats ignore the bool.
    let color = match &args.output {
        Some(_) => false,
        None => global
            .color
            .resolve(std::io::IsTerminal::is_terminal(&io::stdout())),
    };
    let reporter = build_reporter(
        format,
        global.verbose,
        global.show_juror_details,
        color,
        global.agent_budget,
    );
    // Enforce the redaction policy at the dispatch site so every
    // reporter shares one redacted view. The empty `Redactor` already
    // catches shape-based secrets (token prefixes, hex blobs,
    // high-entropy base64). A follow-up will hook resolved
    // `bearer_token_env` names into `Redactor::with_secret_env` once the
    // runner records them alongside the report.
    let redactor = mcptest_core::redaction::Redactor::new();
    let view = report.redacted(&redactor);
    let force = args.force;
    match args.output {
        Some(path) => {
            super::overwrite::ensure_overwritable(&path, force)?;
            let mut file = std::fs::File::create(&path)?;
            reporter.render(&view, &mut file)?;
        }
        None => {
            let stdout = io::stdout();
            let mut handle = stdout.lock();
            reporter.render(&view, &mut handle)?;
        }
    }
    // GitHub Actions annotations compose onto any format: the rendered report
    // goes to the chosen sink, the workflow commands go to stderr so Actions
    // turns them into inline PR annotations.
    if annotations_enabled(global.annotations) {
        mcptest_core::report::reporters::emit_annotations(&view, &mut io::stderr())?;
    }
    Ok(0)
}

/// POST `report` to the configured collector. Returns Ok(0) on a
/// successful upload, Ok(2) when the collector returns an error, and
/// Ok(1) when the CLI is misconfigured (no endpoint, bad URL, etc.).
///
/// Never panics; the upload reporter's contract is that a failed upload
/// must not fail the test run. The CLI surfaces a clear exit code so a
/// human running `mcptest report` interactively can spot the problem.
fn upload_saved_report(report: &mcptest_core::report::Report, upload: &UploadArgs) -> Result<i32> {
    use mcptest_core::report::upload::{UploadConfig, UploadOutcome, UploadReporter};

    let endpoint = match upload.upload_endpoint.as_deref() {
        Some(e) => e,
        None => {
            eprintln!(
                "mcptest report --format upload requires --upload-endpoint <URL> (see schemas/wire/v0.json)"
            );
            return Ok(1);
        }
    };
    let parsed = match url::Url::parse(endpoint) {
        Ok(u) => u,
        Err(e) => {
            eprintln!("mcptest report: invalid --upload-endpoint {endpoint:?}: {e}");
            return Ok(1);
        }
    };
    let mut cfg = UploadConfig::new(parsed);
    if let Some(env_name) = &upload.upload_token_env {
        match std::env::var(env_name) {
            Ok(v) if !v.is_empty() => cfg.bearer_token = Some(v),
            Ok(_) => {
                eprintln!(
                    "mcptest report: env var {env_name} is empty; uploading without Authorization header"
                );
            }
            Err(_) => {
                eprintln!(
                    "mcptest report: env var {env_name} not set; uploading without Authorization header"
                );
            }
        }
    }
    cfg.organization = upload.upload_organization.clone();

    let reporter = match UploadReporter::new(cfg) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("mcptest report: upload client build failed: {e}");
            return Ok(1);
        }
    };
    match reporter.send(report) {
        UploadOutcome::Uploaded {
            run_id,
            status,
            duration_ms,
        } => {
            println!("uploaded run {run_id} (status {status}, {duration_ms}ms)");
            Ok(0)
        }
        UploadOutcome::ClientError {
            status,
            body_excerpt,
        } => {
            eprintln!("mcptest report: collector returned {status}: {body_excerpt}");
            Ok(2)
        }
        UploadOutcome::Skipped { reason } => {
            eprintln!("mcptest report: upload skipped: {reason}");
            Ok(2)
        }
    }
}
