//! L5-15 — Contract tests for `homeostasis_violation_data_v1` and the
//! adjacent emit-site predicate (`exercised > declared`).
//!
//! The supervisor emits this event as a sibling to
//! `dev.cellos.events.cell.authority.v1.homeostasis` when, and only when,
//! the per-cell FlowAccumulator reports more unique outbound connections
//! than the spec declared in `authority.egressRules`. These tests pin:
//!
//!   1. `exercised > declared` → builder returns a payload with `overage`
//!      strictly positive and `severity == "critical"`.
//!   2. `exercised <= declared` → emit-site predicate `exercised > declared`
//!      is false, so the violation event is NOT emitted. (The builder is a
//!      pure JSON helper; the gate lives at the supervisor emit site. We
//!      cover the predicate here as a unit-level assertion so a future
//!      refactor of the supervisor branch is forced to update either this
//!      test or the predicate.)
//!   3. `declared == 0` AND `exercised >= 1` → violation triggers; this is
//!      the strictest case of the authority model — a spec that declares
//!      zero egress rules but observes any connection MUST flag.
//!
//! The on-disk schema at
//! `contracts/schemas/cell-authority-homeostasis-violation-v1.schema.json`
//! is spot-checked so a future drift between the builder and the schema
//! file fails loudly. Same posture as `homeostasis_schema_v1.rs`.

use cellos_core::homeostasis_violation_data_v1;
use serde_json::Value;

/// Schema text, embedded so the test fails at compile time if the file is
/// renamed or removed.
const VIOLATION_SCHEMA_JSON: &str =
    include_str!("../../../contracts/schemas/cell-authority-homeostasis-violation-v1.schema.json");

const SAMPLE_SPEC_HASH: &str =
    "sha256:0000000000000000000000000000000000000000000000000000000000000000";

/// Mirrors the supervisor emit-site predicate. Co-locating it here means a
/// future refactor of the supervisor branch is forced to keep this in sync,
/// because all three behavioral tests in this file route through it.
fn emit_site_should_fire(declared: u64, exercised: u64) -> bool {
    exercised > declared
}

#[test]
fn homeostasis_violation_emitted_when_exercised_exceeds_declared() {
    // Mock: 5 unique flows recorded by the accumulator, spec declared 3
    // egress rules → violation event MUST be emitted.
    let declared: u64 = 3;
    let exercised: u64 = 5;

    assert!(
        emit_site_should_fire(declared, exercised),
        "supervisor predicate must fire when exercised (5) > declared (3)"
    );

    let data =
        homeostasis_violation_data_v1("cell-violation-001", declared, exercised, SAMPLE_SPEC_HASH);

    assert_eq!(data["cellId"], Value::String("cell-violation-001".into()));
    assert_eq!(data["declaredEgressRuleCount"], Value::from(declared));
    assert_eq!(data["exercisedEgressConnections"], Value::from(exercised));
    assert_eq!(
        data["overage"],
        Value::from(exercised - declared),
        "overage must be exercised - declared"
    );
    assert_eq!(data["specHash"], Value::String(SAMPLE_SPEC_HASH.into()));
    assert_eq!(
        data["severity"],
        Value::String("critical".into()),
        "severity is fixed to `critical` per schema enum"
    );
}

#[test]
fn no_violation_when_exercised_within_declared() {
    // Mock: 3 unique flows, 3 declared rules → equality is NOT a violation
    // (authority efficiency 1.0 is the cap, not a breach).
    let declared: u64 = 3;
    let exercised: u64 = 3;

    assert!(
        !emit_site_should_fire(declared, exercised),
        "supervisor predicate must NOT fire when exercised == declared (equality is the upper bound, not a breach)"
    );

    // Boundary: exercised < declared → also no violation.
    assert!(
        !emit_site_should_fire(3, 2),
        "supervisor predicate must NOT fire when exercised < declared"
    );

    // Boundary: exercised == 0, declared == 0 → vacuous, no violation.
    assert!(
        !emit_site_should_fire(0, 0),
        "vacuous case (0 declared, 0 exercised) is not a violation"
    );
}

#[test]
fn zero_egress_spec_always_triggers_violation() {
    // Mock: spec declares 0 egress rules, 1 flow observed → violation MUST
    // fire. This is the strictest case of the authority model: a spec that
    // claims it needs no network MUST be flagged the instant any outbound
    // 5-tuple is recorded.
    let declared: u64 = 0;
    let exercised: u64 = 1;

    assert!(
        emit_site_should_fire(declared, exercised),
        "predicate must fire when spec declares zero egress and any connection is observed"
    );

    let data =
        homeostasis_violation_data_v1("cell-zero-egress", declared, exercised, SAMPLE_SPEC_HASH);

    assert_eq!(data["declaredEgressRuleCount"], Value::from(0u64));
    assert_eq!(data["exercisedEgressConnections"], Value::from(1u64));
    assert_eq!(data["overage"], Value::from(1u64));
    assert_eq!(data["severity"], Value::String("critical".into()));
}

#[test]
fn violation_schema_pins_critical_severity_and_required_fields() {
    // Spot-check the on-disk schema so a refactor of the schema file is
    // forced to either update this test or get caught.
    let schema: Value =
        serde_json::from_str(VIOLATION_SCHEMA_JSON).expect("violation schema must be valid JSON");

    let required = schema["required"]
        .as_array()
        .expect("required must be an array");
    let required_names: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
    for must_have in [
        "cellId",
        "declaredEgressRuleCount",
        "exercisedEgressConnections",
        "overage",
        "specHash",
        "severity",
    ] {
        assert!(
            required_names.contains(&must_have),
            "schema MUST require field `{must_have}`"
        );
    }

    let severity_enum = schema["properties"]["severity"]["enum"]
        .as_array()
        .expect("severity must declare an enum");
    assert_eq!(severity_enum.len(), 1);
    assert_eq!(severity_enum[0], Value::String("critical".into()));

    // overage minimum is 1 — this event must NOT be emitted when overage
    // would be zero or negative (the supervisor guards on exercised > declared).
    assert_eq!(
        schema["properties"]["overage"]["minimum"],
        Value::from(1u64),
        "overage minimum is 1 — this event is not emitted when exercised <= declared"
    );
}
