package hub

import (
	"encoding/json"
	"os"
	"testing"
)

// The desired-state wire is a contract DUPLICATED across two repos (no shared types module yet).
// testdata/desired-state.golden.json and testdata/control-envelope.golden.json MUST be kept
// byte-identical with felhom.eu/hub's copies; these tests decode them through the agent structs
// and key-set-compare, catching drift between the struct and the served shape.
func TestDesiredStateGolden_DecodesAndKeySet(t *testing.T) {
	raw, err := os.ReadFile("testdata/desired-state.golden.json")
	if err != nil {
		t.Fatal(err)
	}
	var resp DesiredStateResponse
	if err := json.Unmarshal(raw, &resp); err != nil {
		t.Fatalf("golden does not decode into DesiredStateResponse: %v", err)
	}
	if resp.Generation != 4 {
		t.Errorf("generation = %d, want 4", resp.Generation)
	}
	if len(resp.DesiredState.Guests) != 2 {
		t.Fatalf("guests = %d, want 2", len(resp.DesiredState.Guests))
	}
	benign := resp.DesiredState.Guests[0]
	if benign.VMID != 100 || benign.Run != "running" || benign.Spec == nil || benign.Spec.Cores != 2 || benign.Description == nil {
		t.Errorf("benign guest = %+v", benign)
	}
	destructive := resp.DesiredState.Guests[1]
	if destructive.VMID != 200 || !destructive.Decommission {
		t.Errorf("destructive guest = %+v, want vmid 200 decommission", destructive)
	}
	if resp.DesiredState.PBSNamespace != "felhom-cust-acme" {
		t.Errorf("pbs_namespace = %q", resp.DesiredState.PBSNamespace)
	}
	if resp.DesiredState.RestoreDirective == nil || resp.DesiredState.RestoreDirective.Mode != "guest_loss" {
		t.Errorf("restore_directive = %+v, want carried (guest_loss)", resp.DesiredState.RestoreDirective)
	}

	// Bidirectional key-set drift guard: the marshaled struct's keys must match the golden's
	// (top-level, the desired_state object, and a guest element).
	var golden map[string]any
	json.Unmarshal(raw, &golden)
	b, _ := json.Marshal(resp)
	var got map[string]any
	json.Unmarshal(b, &got)
	assertSameKeys(t, "<desired-state top>", golden, got)
	assertSameKeys(t, "desired_state", golden["desired_state"], got["desired_state"])
	assertSameKeys(t, "desired_state.guests[0]",
		firstElem(golden["desired_state"].(map[string]any)["guests"]),
		firstElem(got["desired_state"].(map[string]any)["guests"]))
	assertSameKeys(t, "desired_state.restore_directive",
		golden["desired_state"].(map[string]any)["restore_directive"],
		got["desired_state"].(map[string]any)["restore_directive"])
}

func TestControlEnvelopeGolden_Decodes(t *testing.T) {
	raw, err := os.ReadFile("testdata/control-envelope.golden.json")
	if err != nil {
		t.Fatal(err)
	}
	var env ControlEnvelope
	if err := json.Unmarshal(raw, &env); err != nil {
		t.Fatalf("golden does not decode into ControlEnvelope: %v", err)
	}
	if env.Status != "ok" || env.PollIntervalSeconds == nil || *env.PollIntervalSeconds != 900 {
		t.Errorf("envelope poll/status = %+v", env)
	}
	if env.DesiredGeneration != 4 || !env.HasSignedOps || env.Blocked {
		t.Errorf("envelope flags = gen %d signed %v blocked %v", env.DesiredGeneration, env.HasSignedOps, env.Blocked)
	}
}
