package protocol

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

func TestEnvelopeValidate_AgentHello(t *testing.T) {
	payload := AgentHelloPayload{
		NodeID:          "node-01",
		AgentVersion:    "0.1.0",
		ProtocolVersion: CurrentProtocolVersion,
	}
	raw, err := json.Marshal(payload)
	if err != nil {
		t.Fatal(err)
	}

	env := &Envelope{
		ProtocolVersion: CurrentProtocolVersion,
		MessageID:       "msg-001",
		Type:            TypeAgentHello,
		Timestamp:       time.Now().UTC(),
		Payload:         raw,
	}

	if err := env.Validate(); err != nil {
		t.Fatalf("expected valid envelope, got %v", err)
	}
	if !env.Type.IsAgentToControl() {
		t.Fatal("agent.hello should be agent-to-control")
	}
}

func TestEnvelopeValidate_ServerStart(t *testing.T) {
	payload := ServerStartPayload{
		OperationMeta: OperationMeta{
			ServerID:   "srv-01",
			Generation: 4,
		},
		DesiredGeneration: 4,
	}
	raw, err := json.Marshal(payload)
	if err != nil {
		t.Fatal(err)
	}

	env := &Envelope{
		ProtocolVersion: CurrentProtocolVersion,
		MessageID:       "msg-002",
		CorrelationID:   "corr-002",
		Type:            TypeServerStart,
		Timestamp:       time.Now().UTC(),
		Payload:         raw,
	}

	if err := env.Validate(); err != nil {
		t.Fatalf("expected valid envelope, got %v", err)
	}
	if !env.Type.IsControlToAgent() {
		t.Fatal("server.start should be control-to-agent")
	}
}

func TestEnvelopeValidate_RejectsInvalid(t *testing.T) {
	tests := []struct {
		name string
		env  Envelope
	}{
		{
			name: "missing message id",
			env: Envelope{
				ProtocolVersion: 1,
				Type:            TypeAgentHeartbeat,
				Timestamp:       time.Now().UTC(),
				Payload:         json.RawMessage(`{"nodeId":"n1"}`),
			},
		},
		{
			name: "unknown type",
			env: Envelope{
				ProtocolVersion: 1,
				MessageID:       "m1",
				Type:            "agent.unknown",
				Timestamp:       time.Now().UTC(),
				Payload:         json.RawMessage(`{}`),
			},
		},
		{
			name: "non-utc timestamp",
			env: Envelope{
				ProtocolVersion: 1,
				MessageID:       "m1",
				Type:            TypeAgentHeartbeat,
				Timestamp:       time.Now().Local(),
				Payload:         json.RawMessage(`{"nodeId":"n1"}`),
			},
		},
		{
			name: "invalid payload json",
			env: Envelope{
				ProtocolVersion: 1,
				MessageID:       "m1",
				Type:            TypeAgentHeartbeat,
				Timestamp:       time.Now().UTC(),
				Payload:         json.RawMessage(`{not-json`),
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if err := tc.env.Validate(); err == nil {
				t.Fatal("expected validation error")
			}
		})
	}
}

func TestDecodeEnvelope(t *testing.T) {
	data := []byte(`{
		"protocolVersion": 1,
		"messageId": "01HXYZ",
		"type": "agent.heartbeat",
		"timestamp": "2026-01-01T00:00:00Z",
		"payload": {"nodeId":"node-01","runningServers":0}
	}`)

	env, err := DecodeEnvelope(data)
	if err != nil {
		t.Fatalf("decode failed: %v", err)
	}
	if env.Type != TypeAgentHeartbeat {
		t.Fatalf("unexpected type %s", env.Type)
	}
}

func TestServerProvisionPayload_JSONRoundtrip(t *testing.T) {
	deadline := time.Date(2026, 6, 26, 12, 0, 0, 0, time.UTC)
	original := ServerProvisionPayload{
		OperationMeta: OperationMeta{
			ServerID:       "srv-abc123",
			Generation:     2,
			Deadline:       &deadline,
			IdempotencyKey: "idem-001",
		},
		Edition:          "JAVA",
		SoftwareFamily:   "PAPER",
		MinecraftVersion: "1.21.1",
		HostPort:         25565,
		RamMb:            2048,
		DataPath:         "/var/lib/hexahost-gamecloud/srv-abc123",
		EulaAccepted:     true,
	}

	data, err := json.Marshal(original)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var decoded ServerProvisionPayload
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if decoded.ServerID != original.ServerID {
		t.Fatalf("serverId: got %q want %q", decoded.ServerID, original.ServerID)
	}
	if decoded.Generation != original.Generation {
		t.Fatalf("generation: got %d want %d", decoded.Generation, original.Generation)
	}
	if decoded.Edition != original.Edition {
		t.Fatalf("edition: got %q want %q", decoded.Edition, original.Edition)
	}
	if decoded.SoftwareFamily != original.SoftwareFamily {
		t.Fatalf("softwareFamily: got %q want %q", decoded.SoftwareFamily, original.SoftwareFamily)
	}
	if decoded.MinecraftVersion != original.MinecraftVersion {
		t.Fatalf("minecraftVersion: got %q want %q", decoded.MinecraftVersion, original.MinecraftVersion)
	}
	if decoded.HostPort != original.HostPort {
		t.Fatalf("hostPort: got %d want %d", decoded.HostPort, original.HostPort)
	}
	if decoded.RamMb != original.RamMb {
		t.Fatalf("ramMb: got %d want %d", decoded.RamMb, original.RamMb)
	}
	if decoded.DataPath != original.DataPath {
		t.Fatalf("dataPath: got %q want %q", decoded.DataPath, original.DataPath)
	}
	if decoded.EulaAccepted != original.EulaAccepted {
		t.Fatalf("eulaAccepted: got %v want %v", decoded.EulaAccepted, original.EulaAccepted)
	}
	if decoded.IdempotencyKey != original.IdempotencyKey {
		t.Fatalf("idempotencyKey: got %q want %q", decoded.IdempotencyKey, original.IdempotencyKey)
	}
	if decoded.Deadline == nil || !decoded.Deadline.Equal(deadline) {
		t.Fatalf("deadline mismatch: got %v want %v", decoded.Deadline, deadline)
	}
}

func TestNewEnvelope(t *testing.T) {
	env, err := NewEnvelope(TypeAgentHello, "msg-new", AgentHelloPayload{
		NodeID:          "n1",
		AgentVersion:    "0.1.0",
		ProtocolVersion: 1,
	})
	if err != nil {
		t.Fatalf("new envelope: %v", err)
	}
	if env.ProtocolVersion != CurrentProtocolVersion {
		t.Fatalf("protocol version %d", env.ProtocolVersion)
	}
}
