use std::any::{TypeId, type_name};

use fabro_api::types::{
    BlockedReason as ApiBlockedReason, FailureReason as ApiFailureReason,
    PendingReason as ApiPendingReason, RunControlAction as ApiRunControlAction,
    RunStatus as ApiRunStatus, SuccessReason as ApiSuccessReason,
};
use fabro_types::status::{
    BlockedReason, FailureReason, PendingReason, RunControlAction, RunStatus, SuccessReason,
};
use serde::Serialize;
use serde_json::{Value, json};

#[test]
fn status_family_reuses_domain_types() {
    assert_same_type::<ApiRunStatus, RunStatus>();
    assert_same_type::<ApiSuccessReason, SuccessReason>();
    assert_same_type::<ApiFailureReason, FailureReason>();
    assert_same_type::<ApiPendingReason, PendingReason>();
    assert_same_type::<ApiBlockedReason, BlockedReason>();
    assert_same_type::<ApiRunControlAction, RunControlAction>();
}

#[test]
fn run_status_json_matches_openapi_shape() {
    assert_json(
        RunStatus::Submitted,
        json!({
            "kind": "submitted"
        }),
    );
    assert_json(
        RunStatus::Pending {
            reason: PendingReason::ApprovalRequired,
        },
        json!({
            "kind": "pending",
            "reason": "approval_required"
        }),
    );
    assert_json(
        RunStatus::Runnable,
        json!({
            "kind": "runnable"
        }),
    );
    assert_json(
        RunStatus::Starting,
        json!({
            "kind": "starting"
        }),
    );
    assert_json(
        RunStatus::Running,
        json!({
            "kind": "running"
        }),
    );
    assert_json(
        RunStatus::Blocked {
            blocked_reason: BlockedReason::HumanInputRequired,
        },
        json!({
            "kind": "blocked",
            "blocked_reason": "human_input_required"
        }),
    );
    assert_json(
        RunStatus::Paused { prior_block: None },
        json!({
            "kind": "paused",
            "prior_block": null
        }),
    );
    assert_json(
        RunStatus::Removing,
        json!({
            "kind": "removing"
        }),
    );
    assert_json(
        RunStatus::Succeeded {
            reason: SuccessReason::Completed,
        },
        json!({
            "kind": "succeeded",
            "reason": "completed"
        }),
    );
    assert_json(
        RunStatus::Failed {
            reason: FailureReason::Cancelled,
        },
        json!({
            "kind": "failed",
            "reason": "cancelled"
        }),
    );
    assert_json(
        RunStatus::Dead,
        json!({
            "kind": "dead"
        }),
    );
}

#[test]
fn success_reason_json_tokens_match_openapi() {
    assert_string_json(SuccessReason::Completed, "completed");
    assert_string_json(SuccessReason::PartialSuccess, "partial_success");
}

#[test]
fn failure_reason_json_tokens_match_openapi() {
    assert_string_json(FailureReason::WorkflowError, "workflow_error");
    assert_string_json(FailureReason::PublishFailed, "publish_failed");
    assert_string_json(FailureReason::Cancelled, "cancelled");
    assert_string_json(FailureReason::ApprovalDenied, "approval_denied");
    assert_string_json(FailureReason::Terminated, "terminated");
    assert_string_json(FailureReason::TransientInfra, "transient_infra");
    assert_string_json(FailureReason::BudgetExhausted, "budget_exhausted");
    assert_string_json(FailureReason::LaunchFailed, "launch_failed");
    assert_string_json(FailureReason::BootstrapFailed, "bootstrap_failed");
    assert_string_json(FailureReason::SandboxInitFailed, "sandbox_init_failed");
}

#[test]
fn pending_reason_json_tokens_match_openapi() {
    assert_string_json(PendingReason::ApprovalRequired, "approval_required");
}

#[test]
fn blocked_reason_json_tokens_match_openapi() {
    assert_string_json(BlockedReason::HumanInputRequired, "human_input_required");
}

#[test]
fn run_control_action_json_tokens_match_openapi() {
    assert_string_json(RunControlAction::Cancel, "cancel");
    assert_string_json(RunControlAction::Pause, "pause");
    assert_string_json(RunControlAction::Unpause, "unpause");
}

fn assert_same_type<T: 'static, U: 'static>() {
    assert_eq!(
        TypeId::of::<T>(),
        TypeId::of::<U>(),
        "{} should be the same type as {}",
        type_name::<T>(),
        type_name::<U>()
    );
}

fn assert_string_json<T: Serialize>(value: T, expected: &str) {
    assert_eq!(
        serde_json::to_value(value).unwrap(),
        Value::String(expected.into())
    );
}

fn assert_json<T: Serialize>(value: T, expected: Value) {
    assert_eq!(serde_json::to_value(value).unwrap(), expected);
}
