package store

import "testing"

// These tests drive decodeOOBInto — THE REAL DECODE BOUNDARY — with raw report JSON, not a
// hand-built HostOOBRow.
//
// That distinction is the entire point of R-260 and is not a style preference. Every hub test that
// touched OOB before this file constructed a HostOOBRow itself and asked what the checker did with
// it. A test written that way CANNOT SEE A FIELD THAT NEVER DECODES: it hands the struct the value
// the production path would have discarded, and passes. The defect lived under a green suite for
// five weeks because of exactly that.

const oobHealthyJSON = `{"host_id":"h1","oob":{
  "felhom_sshd_active":true,"felhom_sshd_port":8822,"reachable":true,
  "config_invalid":false,"operator_peer_configured":true,
  "operator_key_configured":true,"wg_handshake_age_s":42,"healed_at":"2026-08-08T01:02:03Z"}}`

// The shape the agent sends for the box this whole session is about: everything up, and the key
// that actually grants entry absent.
const oobNoKeyJSON = `{"host_id":"h1","oob":{
  "felhom_sshd_active":true,"felhom_sshd_port":8822,"reachable":true,
  "config_invalid":false,"operator_peer_configured":true,
  "operator_key_configured":false}}`

// An `oob` stanza with NO operator_key_configured key at all. Unreachable for any released agent —
// the field and the stanza shipped together in v0.72.0 — but absence must be structurally
// distinguishable from a reported false, or this defect returns through the version door.
const oobLegacyNoFieldJSON = `{"host_id":"h1","oob":{
  "felhom_sshd_active":true,"felhom_sshd_port":8822,"reachable":true,
  "config_invalid":false,"operator_peer_configured":true}}`

func TestOOBDecode_CarriesOperatorKeyConfigured(t *testing.T) {
	var r HostOOBRow
	if err := decodeOOBInto(&r, oobHealthyJSON); err != nil {
		t.Fatalf("decode: %v", err)
	}
	if !r.Present {
		t.Fatal("stanza present but Present=false")
	}
	// THE ASSERTION THIS FILE EXISTS FOR. Before R-260 the struct had no field, so this line does
	// not compile against the old code — which is the red-proof, recorded rather than described.
	if !r.OperatorKeyConfigured {
		t.Error("operator_key_configured=true on the wire did not reach the row — it is being dropped at the decode boundary, which is R-260")
	}
	if !r.OperatorKeyReported {
		t.Error("the field was on the wire; OperatorKeyReported must be true")
	}
	if r.WGHandshakeAgeS == nil || *r.WGHandshakeAgeS != 42 {
		t.Errorf("wg_handshake_age_s did not decode: %v", r.WGHandshakeAgeS)
	}
	if r.HealedAt != "2026-08-08T01:02:03Z" {
		t.Errorf("healed_at did not decode: %q", r.HealedAt)
	}
}

func TestOOBDecode_ReportedFalseIsDistinctFromNotReported(t *testing.T) {
	var no HostOOBRow
	if err := decodeOOBInto(&no, oobNoKeyJSON); err != nil {
		t.Fatalf("decode: %v", err)
	}
	if no.OperatorKeyConfigured {
		t.Error("operator_key_configured=false decoded as true")
	}
	if !no.OperatorKeyReported {
		t.Error("the agent DID say false; that must be distinguishable from never saying")
	}

	var legacy HostOOBRow
	if err := decodeOOBInto(&legacy, oobLegacyNoFieldJSON); err != nil {
		t.Fatalf("decode: %v", err)
	}
	if legacy.OperatorKeyReported {
		t.Error("no operator_key_configured key on the wire, yet OperatorKeyReported=true — absence is being read as a statement")
	}
	// Both are `false`; only the Reported flag tells them apart. If a future change collapses the
	// pointer to a plain bool, this is the assertion that fails.
	if no.OperatorKeyConfigured != legacy.OperatorKeyConfigured {
		t.Fatal("precondition of this test changed")
	}
}

func TestOOBDecode_AbsentStanzaIsNotPresent(t *testing.T) {
	var r HostOOBRow
	_ = decodeOOBInto(&r, `{"host_id":"h1"}`)
	if r.Present {
		t.Error("no oob stanza, yet Present=true")
	}
	if r.OperatorKeyReported {
		t.Error("no oob stanza, yet OperatorKeyReported=true")
	}
}

func TestOOBDecode_MalformedDegradesToZeroNeverPanics(t *testing.T) {
	var r HostOOBRow
	_ = decodeOOBInto(&r, `{"oob":`) // truncated
	if r.Present {
		t.Error("malformed JSON must not yield Present=true")
	}
}
