package daemon

import (
	"reflect"
	"strings"
	"testing"
)

func TestDefaultSessionLinkCommandRegistryGuardrails(t *testing.T) {
	registry := defaultSessionLinkCommandRegistry()
	descriptors := registry.Descriptors()
	if len(descriptors) == 0 {
		t.Fatal("default session link command registry is empty")
	}
	for _, descriptor := range descriptors {
		if descriptor.Name == "" || descriptor.WireType == "" || descriptor.Domain == "" || descriptor.Kind == "" || descriptor.Direction == "" || descriptor.SchemaVersion <= 0 || descriptor.CapabilityGate == "" || descriptor.LinkPolicy == "" || descriptor.Timeout <= 0 || descriptor.Fallback == "" || descriptor.Owner == "" || descriptor.MetricsName == "" || len(descriptor.TestRequirements) == 0 {
			t.Fatalf("descriptor for %q is missing guardrail metadata: %#v", descriptor.WireType, descriptor)
		}
		if strings.Contains(strings.ToLower(string(descriptor.Domain)), "websocket") || strings.Contains(strings.ToLower(descriptor.Name), "data_ws") {
			t.Fatalf("descriptor %q leaks carrier-specific naming", descriptor.WireType)
		}
	}
}

func TestDefaultSessionLinkCommandRegistryCoversKnownSessionLinkCommands(t *testing.T) {
	expected := []string{
		sessionLinkCommandReady,
		sessionLinkCommandServerCapabilities,
		sessionLinkCommandAgentStart,
		sessionLinkCommandAgentStop,
		sessionLinkCommandAgentUpdateIdentity,
		sessionLinkCommandAgentResetWorkspace,
		sessionLinkCommandAgentDeliver,
		sessionLinkCommandAgentDeliverAck,
		sessionLinkCommandAgentStatus,
		sessionLinkCommandAgentActivity,
		sessionLinkCommandAgentSession,
		sessionLinkCommandAgentTurnState,
		sessionLinkCommandAgentWakeStatus,
		sessionLinkCommandAgentRuntimeEvent,
		sessionLinkCommandAgentRecoveryPointPrepared,
		sessionLinkCommandAgentTrajectory,
		sessionLinkCommandAgentTrajectoryHistory,
		sessionLinkCommandAgentTrajectoryHistoryResult,
		sessionLinkCommandAgentRestoreRecoveryPoint,
		sessionLinkCommandAgentRestoreRecoveryPointDone,
		sessionLinkCommandAgentGCRecoveryPoints,
		sessionLinkCommandAgentMemoryAccessChanged,
		sessionLinkCommandAgentAssetsAccessChanged,
		sessionLinkCommandAgentWorkspaceList,
		sessionLinkCommandAgentWorkspaceFileTree,
		sessionLinkCommandAgentWorkspaceRead,
		sessionLinkCommandAgentWorkspaceFileContent,
		sessionLinkCommandAgentWorkspaceApplySeedFiles,
		sessionLinkCommandAgentWorkspaceApplySeedDone,
		sessionLinkCommandAgentWorkspaceDelete,
		sessionLinkCommandAgentWorkspaceDeleteResult,
		sessionLinkCommandAgentReviewSummary,
		sessionLinkCommandAgentReviewSummaryResult,
		sessionLinkCommandAgentReviewFiles,
		sessionLinkCommandAgentReviewFilesResult,
		sessionLinkCommandAgentReviewDiff,
		sessionLinkCommandAgentReviewDiffResult,
		sessionLinkCommandOutputTargetValidate,
		sessionLinkCommandOutputTargetValidateResult,
		sessionLinkCommandMachineWorkspaceScan,
		sessionLinkCommandMachineWorkspaceScanResult,
		sessionLinkCommandMachineWorkspaceDelete,
		sessionLinkCommandMachineWorkspaceDeleteResult,
		sessionLinkCommandMachineWorkspacesList,
		sessionLinkCommandMachineWorkspacesListResponse,
		sessionLinkCommandMachineAgentList,
		sessionLinkCommandMachineAgentListResponse,
		sessionLinkCommandElasticRequestOpen,
		sessionLinkCommandElasticRequestDrain,
		sessionLinkCommandElasticRequestCancel,
		sessionLinkCommandRemoteRequestAssign,
		sessionLinkCommandRemoteRequestResult,
	}
	registry := defaultSessionLinkCommandRegistry()
	for _, wireType := range expected {
		descriptor, ok := registry.Lookup(wireType)
		if !ok {
			t.Fatalf("expected command %q to be registered", wireType)
		}
		if descriptor.WireType != wireType {
			t.Fatalf("lookup(%q) returned %q", wireType, descriptor.WireType)
		}
	}
	if got, want := len(registry.Descriptors()), len(expected); got != want {
		t.Fatalf("registry command count = %d, want %d", got, want)
	}
}

func TestSessionLinkCompatibilityExceptionsAreDocumented(t *testing.T) {
	registry := defaultSessionLinkCommandRegistry()
	expected := map[string]CommandDomain{
		sessionLinkCompatibilityPing:       CommandDomainSession,
		sessionLinkCompatibilityPong:       CommandDomainSession,
		sessionLinkCompatibilityAgentSleep: CommandDomainAgentLifecycle,
	}
	if got, want := len(sessionLinkCompatibilityExceptions), len(expected); got != want {
		t.Fatalf("compatibility exception count = %d, want %d", got, want)
	}
	seen := make(map[string]struct{}, len(sessionLinkCompatibilityExceptions))
	for _, exception := range sessionLinkCompatibilityExceptions {
		if strings.TrimSpace(exception.WireType) == "" || strings.TrimSpace(exception.Reason) == "" || exception.Domain == "" || exception.Direction == "" {
			t.Fatalf("compatibility exception missing metadata: %#v", exception)
		}
		if _, registered := registry.Lookup(exception.WireType); registered {
			t.Fatalf("compatibility exception %q must not duplicate a typed Command Registry descriptor", exception.WireType)
		}
		if _, duplicate := seen[exception.WireType]; duplicate {
			t.Fatalf("duplicate compatibility exception %q", exception.WireType)
		}
		seen[exception.WireType] = struct{}{}
		if wantDomain, ok := expected[exception.WireType]; !ok {
			t.Fatalf("unexpected compatibility exception %q", exception.WireType)
		} else if exception.Domain != wantDomain {
			t.Fatalf("compatibility exception %q domain = %q, want %q", exception.WireType, exception.Domain, wantDomain)
		}
	}
}

func TestDefaultSessionLinkCommandRegistryInboundHandlers(t *testing.T) {
	for _, descriptor := range defaultSessionLinkCommandRegistry().Descriptors() {
		if descriptor.Direction != CommandDirectionDesktopToServer {
			continue
		}
		if defaultSessionLinkCommandHandlers[descriptor.WireType] == nil {
			t.Fatalf("desktop-to-server command %q has no typed handler", descriptor.WireType)
		}
	}
}

// T2: registry-consistency (inverse direction) — every desktop-to-server typed
// handler must have a Command Registry descriptor, so a frame routed by
// handleMessage reaches the handler instead of being silently dropped. This
// would have caught the missing agent:turn_state descriptor in rounds 4/5.
func TestDefaultSessionLinkCommandRegistryInboundHandlersHaveDescriptors(t *testing.T) {
	registry := defaultSessionLinkCommandRegistry()
	for wireType, handler := range defaultSessionLinkCommandHandlers {
		if handler == nil {
			continue
		}
		descriptor, ok := registry.Lookup(wireType)
		if !ok {
			t.Fatalf("desktop-to-server handler %q has no Command Registry descriptor; handleMessage would drop its frames", wireType)
		}
		if descriptor.Direction != CommandDirectionDesktopToServer {
			t.Fatalf("desktop-to-server handler %q descriptor direction = %q, want desktop_to_server", wireType, descriptor.Direction)
		}
	}
}

func TestDefaultSessionLinkCommandRegistryTypedPayloads(t *testing.T) {
	checks := map[string]any{
		sessionLinkCommandServerCapabilities:        (*serverCapabilitiesCommand)(nil),
		sessionLinkCommandAgentWorkspaceRead:        (*agentWorkspaceReadCommand)(nil),
		sessionLinkCommandAgentWorkspaceFileContent: (*fileContentMessage)(nil),
		sessionLinkCommandAgentReviewSummary:        (*agentReviewSummaryCommand)(nil),
		sessionLinkCommandAgentReviewSummaryResult:  (*reviewSummaryResultMessage)(nil),
	}
	registry := defaultSessionLinkCommandRegistry()
	for wireType, want := range checks {
		descriptor, ok := registry.Lookup(wireType)
		if !ok {
			t.Fatalf("expected command %q to be registered", wireType)
		}
		if got := descriptor.RequestType; reflect.TypeOf(got) != reflect.TypeOf(want) {
			t.Fatalf("request type for %q = %T, want %T", wireType, got, want)
		}
	}
}

func TestMachineReverseQueryCommandsRemainSessionOnly(t *testing.T) {
	registry := defaultSessionLinkCommandRegistry()
	for _, wireType := range []string{sessionLinkCommandMachineWorkspacesList, sessionLinkCommandMachineAgentList} {
		descriptor, ok := registry.Lookup(wireType)
		if !ok {
			t.Fatalf("expected reverse query command %q to be registered", wireType)
		}
		if descriptor.Direction != CommandDirectionDesktopToServer {
			t.Fatalf("%s direction = %q, want desktop_to_server", wireType, descriptor.Direction)
		}
		if descriptor.LinkPolicy != CommandLinkPolicySessionOnly {
			t.Fatalf("%s link policy = %q, want session_only", wireType, descriptor.LinkPolicy)
		}
		if descriptor.Fallback != commandFallbackNone {
			t.Fatalf("%s fallback = %q, want none", wireType, descriptor.Fallback)
		}
	}
}

func TestCommandRegistryRejectsInvalidDescriptors(t *testing.T) {
	_, err := NewCommandRegistry([]CommandDescriptor{{WireType: "agent:test"}})
	if err != ErrCommandDescriptorInvalid {
		t.Fatalf("NewCommandRegistry invalid error = %v, want %v", err, ErrCommandDescriptorInvalid)
	}

	d := descriptor(commandDescriptorSpec{
		name:           "agent.test",
		wireType:       "agent:test",
		domain:         CommandDomainAgentDelivery,
		kind:           CommandKindEvent,
		direction:      CommandDirectionDesktopToServer,
		owner:          commandOwnerAgentRuntime,
		capabilityGate: commandCapabilityNone,
		linkPolicy:     CommandLinkPolicySessionOnly,
		fallback:       commandFallbackNone,
		critical:       false,
	})
	_, err = NewCommandRegistry([]CommandDescriptor{d, d})
	if err != ErrCommandAlreadyRegistered {
		t.Fatalf("NewCommandRegistry duplicate error = %v, want %v", err, ErrCommandAlreadyRegistered)
	}
}
