package drivercontract

import (
	"reflect"
	"strings"
	"testing"

	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

type contractField struct {
	Name string
	Type string
}

// These tests intentionally pin the shared driver contract shape. They are not
// saying the structs can never evolve; they force a reviewer-visible update when
// a field is added, removed, reordered, or widened so runtime-private concerns do
// not quietly leak into the shared process/driver boundary.
func TestLaunchConfigContractShapeIsIntentional(t *testing.T) {
	assertStructFields(t, reflect.TypeOf(LaunchConfig{}), []contractField{
		{Name: "AgentID", Type: "string"},
		{Name: "RuntimeType", Type: "runtimeconfig.RuntimeType"},
		{Name: "SessionID", Type: "string"},
		{Name: "LaunchID", Type: "string"},
		{Name: "WorkDir", Type: "string"},
		{Name: "RuntimeStateDir", Type: "string"},
		{Name: "DisableRecovery", Type: "bool"},
		{Name: "CompiledPrompt", Type: "*promptcompiler.CompiledPrompt"},
		{Name: "PromptAttachments", Type: "[]types.RuntimeAttachment"},
		{Name: "ContractCarrierFile", Type: "string"},
		{Name: "Binary", Type: "string"},
		{Name: "PermissionMode", Type: "string"},
		{Name: "Model", Type: "string"},
		{Name: "SmallModel", Type: "string"},
		{Name: "ReasoningEffort", Type: "string"},
		{Name: "Env", Type: "map[string]string"},
		{Name: "Provider", Type: "*drivercontract.ProviderConfig"},
		{Name: "Exec", Type: "*drivercontract.ExecConfig"},
		{Name: "RuntimeDebugEnabled", Type: "bool"},
		{Name: "DisableNativeTools", Type: "bool"},
		{Name: "MCPs", Type: "[]types.NamedMCPConfig"},
		{Name: "DisallowedTools", Type: "[]string"},
		{Name: "InitialDeliveryMessageID", Type: "string"},
		{Name: "SkillDir", Type: "string"},
		{Name: "ManagedSkillDirs", Type: "[]string"},
		{Name: "AnalysisToken", Type: "string"},
		{Name: "BuiltinSkills", Type: "[]drivercontract.BuiltinSkill"},
		{Name: "SkillPlan", Type: "promptcompiler.SkillPlan"},
	})
}

func TestBuiltinSkillContractShapeIsIntentional(t *testing.T) {
	assertStructFields(t, reflect.TypeOf(BuiltinSkill{}), []contractField{
		{Name: "Name", Type: "string"},
		{Name: "Content", Type: "string"},
	})
}

func TestExecConfigContractShapeIsMinimal(t *testing.T) {
	assertStructFields(t, reflect.TypeOf(ExecConfig{}), []contractField{
		{Name: "BaseURL", Type: "string"},
		{Name: "AuthorizationID", Type: "string"},
		{Name: "TokenFile", Type: "string"},
	})
}

func TestParsedEventContractShapeIsIntentional(t *testing.T) {
	assertStructFields(t, reflect.TypeOf(ParsedEvent{}), []contractField{
		{Name: "Kind", Type: "string"},
		{Name: "Text", Type: "string"},
		{Name: "ToolName", Type: "string"},
		{Name: "ToolCallID", Type: "string"},
		{Name: "ToolInput", Type: "interface {}"},
		{Name: "PromptKind", Type: "string"},
		{Name: "Runtime", Type: "string"},
		{Name: "EventType", Type: "string"},
		{Name: "SessionID", Type: "string"},
		{Name: "RuntimeInputID", Type: "string"},
		{Name: "Message", Type: "string"},
		{Name: "Title", Type: "string"},
		{Name: "SectionID", Type: "string"},
		{Name: "Metadata", Type: "map[string]interface {}"},
		{Name: "StopReason", Type: "string"},
		{Name: "InputTokens", Type: "uint64"},
		{Name: "CachedInputTokens", Type: "uint64"},
		{Name: "OutputTokens", Type: "uint64"},
		{Name: "ActiveContextTokens", Type: "uint64"},
		{Name: "ContextWindowTokens", Type: "uint64"},
		{Name: "Model", Type: "string"},
		{Name: "ModelInvocationID", Type: "string"},
		{Name: "ContextExposureKnown", Type: "bool"},
		{Name: "ContextExposureExcluded", Type: "bool"},
		{Name: "ContextCompacted", Type: "bool"},
	})
}

func TestProviderConfigContractShapeIsIntentional(t *testing.T) {
	assertStructFields(t, reflect.TypeOf(ProviderConfig{}), []contractField{
		{Name: "ID", Type: "string"},
		{Name: "Name", Type: "string"},
		{Name: "BaseURL", Type: "string"},
		{Name: "APIKey", Type: "string"},
		{Name: "MetricsAPIKey", Type: "string"},
		{Name: "CredentialKind", Type: "string"},
		{Name: "EnvKey", Type: "string"},
		{Name: "Headers", Type: "map[string]string"},
		{Name: "WireAPIs", Type: "[]string"},
	})
}

func TestLaunchConfigEffectivePermissionMode(t *testing.T) {
	tests := []struct {
		name      string
		input     string
		want      string
		wantError string
	}{
		{
			name:  "legacy empty defaults full access",
			input: "",
			want:  types.AgentPermissionModeFullAccess,
		},
		{
			name:  "legacy workspace write defaults full access",
			input: types.AgentPermissionModeWorkspaceWrite,
			want:  types.AgentPermissionModeFullAccess,
		},
		{
			name:  "full access",
			input: types.AgentPermissionModeFullAccess,
			want:  types.AgentPermissionModeFullAccess,
		},
		{
			name:      "removed knowledge Q&A scratch is rejected",
			input:     "knowledge_qa_scratch",
			wantError: `unsupported agent permission mode "knowledge_qa_scratch"`,
		},
		{
			name:  "legacy restricted defaults full access",
			input: "restricted",
			want:  types.AgentPermissionModeFullAccess,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := (LaunchConfig{PermissionMode: tc.input}).EffectivePermissionMode()
			if tc.wantError != "" {
				if err == nil || !strings.Contains(err.Error(), tc.wantError) {
					t.Fatalf("EffectivePermissionMode() error = %v, want containing %q", err, tc.wantError)
				}
				return
			}
			if err != nil {
				t.Fatalf("EffectivePermissionMode() error = %v", err)
			}
			if got != tc.want {
				t.Fatalf("EffectivePermissionMode() = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestOptionalDriverCapabilitiesStayNarrow(t *testing.T) {
	tests := []struct {
		name    string
		typ     reflect.Type
		methods []string
	}{
		{name: "RecoveryDriver", typ: reflect.TypeOf((*RecoveryDriver)(nil)).Elem(), methods: []string{"PrepareRecoveryPoint", "RestoreRecoveryPoint"}},
		{name: "MessageBoundRecoveryDriver", typ: reflect.TypeOf((*MessageBoundRecoveryDriver)(nil)).Elem(), methods: []string{"PrepareRecoveryPointForMessage"}},
		{name: "DetachedRecoveryDriver", typ: reflect.TypeOf((*DetachedRecoveryDriver)(nil)).Elem(), methods: []string{"RestoreRecoveryPointDetached"}},
		{name: "BusyStdinDeliveryDriver", typ: reflect.TypeOf((*BusyStdinDeliveryDriver)(nil)).Elem(), methods: []string{"SupportsBusyStdinDelivery"}},
		{name: "BusyStdinMessageDriver", typ: reflect.TypeOf((*BusyStdinMessageDriver)(nil)).Elem(), methods: []string{"DeliverBusyStdinInput"}},
		{name: "RuntimeInputDeliveryDriver", typ: reflect.TypeOf((*RuntimeInputDeliveryDriver)(nil)).Elem(), methods: []string{"DeliverRuntimeInput"}},
		{name: "CorrelatedRuntimeInputDeliveryDriver", typ: reflect.TypeOf((*CorrelatedRuntimeInputDeliveryDriver)(nil)).Elem(), methods: []string{"SupportsCorrelatedRuntimeInputDelivery"}},
		{name: "AckedBusyRuntimeInputDriver", typ: reflect.TypeOf((*AckedBusyRuntimeInputDriver)(nil)).Elem(), methods: []string{"DeliverAckedBusyRuntimeInput", "SupportsAckedBusyRuntimeInputDelivery"}},
		{name: "ActiveRunInterruptDriver", typ: reflect.TypeOf((*ActiveRunInterruptDriver)(nil)).Elem(), methods: []string{"InterruptActiveRun"}},
		{name: "ActiveTurnIDProvider", typ: reflect.TypeOf((*ActiveTurnIDProvider)(nil)).Elem(), methods: []string{"CurrentActiveTurnID"}},
		{name: "IdleCompactionDriver", typ: reflect.TypeOf((*IdleCompactionDriver)(nil)).Elem(), methods: []string{"StartIdleCompaction"}},
		{name: "ContextCompactionDriver", typ: reflect.TypeOf((*ContextCompactionDriver)(nil)).Elem(), methods: []string{"QueueContextCompaction"}},
		{name: "ContextWindowExceededDriver", typ: reflect.TypeOf((*ContextWindowExceededDriver)(nil)).Elem(), methods: []string{"IsContextWindowExceeded"}},
		{name: "OutputTruncationDriver", typ: reflect.TypeOf((*OutputTruncationDriver)(nil)).Elem(), methods: []string{"IsOutputTruncated"}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if tc.typ.NumMethod() != len(tc.methods) {
				t.Fatalf("%s has %d methods, want %d", tc.name, tc.typ.NumMethod(), len(tc.methods))
			}
			for i, want := range tc.methods {
				if got := tc.typ.Method(i).Name; got != want {
					t.Fatalf("%s method %d = %s, want %s", tc.name, i, got, want)
				}
			}
		})
	}
}

func assertStructFields(t *testing.T, typ reflect.Type, want []contractField) {
	t.Helper()
	if typ.NumField() != len(want) {
		t.Fatalf("%s has %d fields, want %d", typ.Name(), typ.NumField(), len(want))
	}
	for i, expected := range want {
		field := typ.Field(i)
		if field.Name != expected.Name || field.Type.String() != expected.Type {
			t.Fatalf("%s field %d = %s %s, want %s %s", typ.Name(), i, field.Name, field.Type, expected.Name, expected.Type)
		}
	}
}
