/* Copyright 2026 Rhett Creighton - Apache License 2.0 */

#include "test/test_helpers.h"

#include "config/runtime.h"
#include "framework/condition.h"
#include "net/snapshot_sync_contract.h"

#include <string.h>

#define SNS_CHECK(name, expr) do { \
    printf("snapshot_negotiation_stalled_condition: %s... ", (name)); \
    if (expr) printf("OK\n"); \
    else { printf("FAIL\n"); failures++; } \
} while (0)

void register_snapshot_negotiation_stalled(void);
void snapshot_negotiation_stalled_test_reset(void);
int snapshot_negotiation_stalled_test_remedy_calls(void);
int64_t snapsync_now_us_internal(void);

static void reset_sns(struct snapshot_sync_service *svc,
                      struct app_runtime_context *runtime)
{
    condition_engine_reset_for_testing();
    snapshot_negotiation_stalled_test_reset();
    memset(svc, 0, sizeof(*svc));
    memset(runtime, 0, sizeof(*runtime));
    snapsync_init(svc, NULL);
    runtime->snapshot_sync = svc;
    app_runtime_set_current(runtime);
}

static void cleanup_sns(void)
{
    app_runtime_set_current(NULL);
    condition_engine_reset_for_testing();
}

int test_snapshot_negotiation_stalled_condition(void)
{
    printf("\n=== snapshot_negotiation_stalled condition tests ===\n");
    int failures = 0;

    {
        struct snapshot_sync_service svc;
        struct app_runtime_context runtime;
        reset_sns(&svc, &runtime);
        bool ok = true;
        register_snapshot_negotiation_stalled();

        svc.state = SNAPSYNC_NEGOTIATING;
        svc.offered_height = 3000000;
        svc.offered_count = 1350000;
        svc.serving_peer_id = 11;
        svc.start_time_us =
            snapsync_now_us_internal() -
            ((int64_t)SNAPSYNC_NEGOTIATION_TIMEOUT_SECS + 1) * 1000000LL;

        condition_engine_tick();
        ok = ok && snapshot_negotiation_stalled_test_remedy_calls() == 1;
        ok = ok && svc.state == SNAPSYNC_IDLE;
        ok = ok && snapsync_is_peer_blacklisted(&svc, 11);
        /* Honest witness (Law 7): the remedy reset + blacklisted the dead
         * peer, but the symptom has not yet MOVED — no fresh negotiation is
         * advancing, so the condition stays active (unwitnessed). The reset's
         * real forward progress is that a DIFFERENT peer can now negotiate. */
        ok = ok && condition_engine_get_active_count() == 1;
        svc.state = SNAPSYNC_NEGOTIATING;
        svc.serving_peer_id = 14;
        svc.offered_height = 3000001;
        svc.offered_count = 1350001;
        condition_engine_tick();
        ok = ok && condition_engine_get_active_count() == 0;
        SNS_CHECK("stalled negotiation resets and blacklists peer", ok);
        cleanup_sns();
    }

    {
        struct snapshot_sync_service svc;
        struct app_runtime_context runtime;
        reset_sns(&svc, &runtime);
        bool ok = true;
        register_snapshot_negotiation_stalled();

        svc.state = SNAPSYNC_NEGOTIATING;
        svc.offered_height = 3000000;
        svc.offered_count = 1350000;
        svc.serving_peer_id = 12;
        svc.start_time_us = snapsync_now_us_internal();

        condition_engine_tick();
        ok = ok && snapshot_negotiation_stalled_test_remedy_calls() == 0;
        ok = ok && svc.state == SNAPSYNC_NEGOTIATING;
        ok = ok && !snapsync_is_peer_blacklisted(&svc, 12);
        SNS_CHECK("fresh negotiation is not reset", ok);
        cleanup_sns();
    }

    {
        struct snapshot_sync_service svc;
        struct app_runtime_context runtime;
        reset_sns(&svc, &runtime);
        bool ok = true;

        svc.state = SNAPSYNC_RECEIVING;
        svc.offered_height = 3000000;
        svc.offered_count = 1350000;
        svc.serving_peer_id = 13;
        svc.start_time_us =
            snapsync_now_us_internal() -
            ((int64_t)SNAPSYNC_NEGOTIATION_TIMEOUT_SECS + 1) * 1000000LL;

        struct snapsync_negotiation_status st;
        snapsync_get_negotiation_status(&svc, &st);
        ok = ok && !st.negotiating;
        ok = ok && !st.stalled;
        ok = ok && !snapsync_check_negotiation_stall();
        ok = ok && svc.state == SNAPSYNC_RECEIVING;
        SNS_CHECK("receive state is not treated as negotiation stall", ok);
        cleanup_sns();
    }

    cleanup_sns();
    return failures;
}
