use fabro_test::{fabro_snapshot, json_elapsed_ms_snapshot_filters, test_context};
use httpmock::MockServer;
use serde_json::json;

use super::support::{
    remote_run_summary_json, setup_seeded_completed_dry_run, setup_seeded_created_dry_run,
};
use crate::support::{run_projection_json, unique_run_id};

fn remote_run_summary(run_id: &str, status: &serde_json::Value) -> serde_json::Value {
    remote_run_summary_json(
        run_id,
        "Blocked Remote Workflow",
        "blocked-remote-workflow",
        "Wait for approval",
        status,
        "2026-04-19T12:00:00Z",
    )
}

#[test]
fn help() {
    let context = test_context!();
    let mut cmd = context.command();
    cmd.args(["wait", "--help"]);
    fabro_snapshot!(context.filters(), cmd, @"
    success: true
    exit_code: 0
    ----- stdout -----
    Block until a workflow run completes

    Usage: fabro wait [OPTIONS] <RUN>

    Arguments:
      <RUN>  Run ID prefix or workflow name (most recent run)

    Options:
          --json               Output as JSON [env: FABRO_JSON=]
          --server <SERVER>    Fabro server target: http(s) URL or absolute Unix socket path [env: FABRO_SERVER=]
          --debug              Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
          --timeout <SECONDS>  Maximum time to wait in seconds
          --interval <MS>      Poll interval in milliseconds [default: 1000]
          --no-upgrade-check   Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
          --quiet              Suppress non-essential output [env: FABRO_QUIET=]
          --verbose            Enable verbose output [env: FABRO_VERBOSE=]
      -h, --help               Print help
    ----- stderr -----
    ");
}

#[test]
fn wait_completed_run_prints_success_summary() {
    let context = test_context!();
    let run = setup_seeded_completed_dry_run(&context);
    let mut filters = context.filters();
    filters.push((
        r"\b\d+(\.\d+)?(ms|s)\b".to_string(),
        "[DURATION]".to_string(),
    ));
    let mut cmd = context.command();
    cmd.args(["wait", &run.run_id]);

    fabro_snapshot!(filters, cmd, @"
    success: true
    exit_code: 0
    ----- stdout -----
    ----- stderr -----
    Succeeded [ULID]  [DURATION]
    ");
}

#[test]
fn wait_completed_run_reads_store_without_status_or_conclusion_files() {
    let context = test_context!();
    let run = setup_seeded_completed_dry_run(&context);
    let mut filters = context.filters();
    filters.push((
        r"\b\d+(\.\d+)?(ms|s)\b".to_string(),
        "[DURATION]".to_string(),
    ));
    let mut cmd = context.command();
    cmd.args(["wait", &run.run_id]);

    fabro_snapshot!(filters, cmd, @"
    success: true
    exit_code: 0
    ----- stdout -----
    ----- stderr -----
    Succeeded [ULID]  [DURATION]
    ");
}

#[test]
fn wait_completed_run_json_outputs_status_and_duration() {
    let context = test_context!();
    let run = setup_seeded_completed_dry_run(&context);
    let filters = json_elapsed_ms_snapshot_filters(context.filters());
    let mut cmd = context.command();
    cmd.args(["wait", "--json", &run.run_id]);

    fabro_snapshot!(filters, cmd, @r###"
    success: true
    exit_code: 0
    ----- stdout -----
    {
      "run_id": "[ULID]",
      "status": "succeeded",
      "timing": {
        "wall_time_ms": "[WALL_TIME_MS]",
        "inference_time_ms": "[INFERENCE_TIME_MS]",
        "tool_time_ms": "[TOOL_TIME_MS]",
        "active_time_ms": "[ACTIVE_TIME_MS]"
      }
    }
    ----- stderr -----
    "###);
}

#[test]
fn wait_submitted_run_times_out() {
    let context = test_context!();
    let run = setup_seeded_created_dry_run(&context);
    let mut cmd = context.command();
    cmd.args(["wait", "--timeout", "0", "--interval", "10", &run.run_id]);

    fabro_snapshot!(context.filters(), cmd, @"
    success: false
    exit_code: 1
    ----- stdout -----
    ----- stderr -----
      × Timed out after 0s waiting for run '[ULID]'
    ");
}

#[test]
fn wait_blocked_run_times_out_without_treating_it_as_terminal() {
    let context = test_context!();
    let run_id = unique_run_id();
    let server = MockServer::start();
    let summary = remote_run_summary(
        run_id.as_str(),
        &json!({
            "kind": "blocked",
            "blocked_reason": "human_input_required"
        }),
    );

    let resolve_run = server.mock(|when, then| {
        when.method("GET")
            .path("/api/v1/runs/resolve")
            .query_param("selector", run_id.as_str());
        then.status(200)
            .header("content-type", "application/json")
            .body(summary.clone().to_string());
    });
    let retrieve_run = server.mock(|when, then| {
        when.method("GET")
            .path(format!("/api/v1/runs/{}", run_id.as_str()));
        then.status(200)
            .header("content-type", "application/json")
            .body(summary.to_string());
    });
    let run_state = server.mock(|when, then| {
        when.method("GET")
            .path(format!("/api/v1/runs/{}/state", run_id.as_str()));
        then.status(200)
            .header("content-type", "application/json")
            .body(
                run_projection_json(
                    run_id.as_str(),
                    &json!({
                        "kind": "blocked",
                        "blocked_reason": "human_input_required"
                    }),
                )
                .to_string(),
            );
    });

    let mut cmd = context.command();
    cmd.args([
        "wait",
        "--server",
        &format!("{}/api/v1", server.base_url()),
        "--timeout",
        "0",
        "--interval",
        "10",
        run_id.as_str(),
    ]);

    fabro_snapshot!(context.filters(), cmd, @"
    success: false
    exit_code: 1
    ----- stdout -----
    ----- stderr -----
      × Timed out after 0s waiting for run '[ULID]'
    ");
    resolve_run.assert();
    assert!(
        retrieve_run.calls() > 0,
        "wait should keep polling the blocked run summary until timeout"
    );
    assert_eq!(
        run_state.calls(),
        0,
        "wait should not fetch run state when the run never becomes terminal"
    );
}
