package contract

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

func TestQueryContractDoesNotExposeRawFilterHelper(t *testing.T) {
	queryType := reflect.TypeOf((*Query)(nil)).Elem()
	if _, ok := queryType.MethodByName("Query"); !ok {
		t.Fatal("Query contract must expose Query")
	}
	if _, ok := queryType.MethodByName("SearchWithSpec"); !ok {
		t.Fatal("Query contract must expose SearchWithSpec")
	}
	if _, ok := queryType.MethodByName("GlobalSearchWithSpec"); !ok {
		t.Fatal("Query contract must expose GlobalSearchWithSpec")
	}
	history, ok := queryType.MethodByName("QueryHistory")
	if !ok {
		t.Fatal("Query contract must expose QueryHistory")
	}
	if strings.Contains(history.Type.String(), "models.QueryResult") {
		t.Fatalf("QueryHistory exposes models carrier: %s", history.Type)
	}
	if _, ok := queryType.MethodByName("QueryWithFilter"); ok {
		t.Fatal("Query contract must not expose raw filter-expression helpers")
	}
	if _, ok := queryType.MethodByName("QueryWithSpec"); ok {
		t.Fatal("Query contract must not expose the legacy query result carrier")
	}
	if _, ok := queryType.MethodByName("SearchSingleObject"); ok {
		t.Fatal("Query contract must not expose raw search filter-expression helpers")
	}
	if _, ok := queryType.MethodByName("GlobalSearch"); ok {
		t.Fatal("Query contract must not expose wire-shaped global search helpers")
	}
}

func TestQueryResultHasNoWirePaginationFieldOrJSONTags(t *testing.T) {
	resultType := reflect.TypeOf(QueryResult{})
	if _, ok := resultType.FieldByName("NextRecordsUrl"); ok {
		t.Fatal("QueryResult must not carry dialect-owned nextRecordsUrl")
	}
	for i := 0; i < resultType.NumField(); i++ {
		field := resultType.Field(i)
		if tag := field.Tag.Get("json"); tag != "" {
			t.Fatalf("QueryResult.%s has json tag %q; dialect mappers own wire tags", field.Name, tag)
		}
	}
}

func TestRecordCarrierHasNoWireMethodsOrTags(t *testing.T) {
	recordType := reflect.TypeOf(Record{})
	if recordType.Kind() != reflect.Map {
		t.Fatalf("Record kind = %s, want map carrier", recordType.Kind())
	}
	if recordType.Name() != "Record" {
		t.Fatalf("Record type name = %q", recordType.Name())
	}
}

func TestRecordsContractDoesNotExposeSObjectCarrier(t *testing.T) {
	recordsType := reflect.TypeOf((*Records)(nil)).Elem()
	for i := 0; i < recordsType.NumMethod(); i++ {
		method := recordsType.Method(i)
		if strings.Contains(method.Type.String(), "models.SObject") {
			t.Fatalf("Records.%s exposes SObject carrier: %s", method.Name, method.Type)
		}
	}
}

func TestFlowExecutorContractUsesNeutralResult(t *testing.T) {
	executorType := reflect.TypeOf((*FlowExecutor)(nil)).Elem()
	method, ok := executorType.MethodByName("ExecuteFlow")
	if !ok {
		t.Fatal("FlowExecutor.ExecuteFlow missing")
	}
	if strings.Contains(method.Type.String(), "models.FlowExecutionResult") {
		t.Fatalf("ExecuteFlow exposes FlowExecutionResult carrier: %s", method.Type)
	}

	errorType := reflect.TypeOf(FlowExecutionError{})
	if _, ok := errorType.FieldByName("StatusCode"); ok {
		t.Fatal("FlowExecutionError must not carry v63 statusCode-shaped field names")
	}
	if _, ok := errorType.FieldByName("Code"); !ok {
		t.Fatal("FlowExecutionError.Code missing")
	}
	for _, typ := range []reflect.Type{reflect.TypeOf(FlowExecutionResult{}), errorType} {
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			if field.Tag.Get("json") != "" {
				t.Fatalf("%s.%s has wire JSON tag %q", typ.Name(), field.Name, field.Tag.Get("json"))
			}
		}
	}
}

func TestPolicyContractUsesSingleNeutralAccessSurface(t *testing.T) {
	policyType := reflect.TypeOf((*Policy)(nil)).Elem()
	method, ok := policyType.MethodByName("Access")
	if !ok {
		t.Fatal("Policy.Access missing")
	}
	if strings.Contains(method.Type.String(), "models.EffectiveObjectAccess") {
		t.Fatalf("Access exposes models carrier: %s", method.Type)
	}
	if _, ok := policyType.MethodByName("ObjectAccess"); ok {
		t.Fatal("Policy.ObjectAccess must not exist; object CRUD is projected through Policy.Access")
	}
	if _, ok := policyType.MethodByName("EffectiveAccess"); ok {
		t.Fatal("Policy.EffectiveAccess must not exist; full access is projected through Policy.Access")
	}

	for _, typ := range []reflect.Type{
		reflect.TypeOf(AccessRequest{}),
		reflect.TypeOf(ObjectAccess{}),
		reflect.TypeOf(FieldAccess{}),
		reflect.TypeOf(RecordTypeAvailability{}),
		reflect.TypeOf(EffectiveObjectAccess{}),
	} {
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			if field.Tag.Get("json") != "" {
				t.Fatalf("%s.%s has wire JSON tag %q", typ.Name(), field.Name, field.Tag.Get("json"))
			}
		}
	}
}

func TestDepsFieldsAreInterfaces(t *testing.T) {
	depsType := reflect.TypeOf(Deps{})
	if depsType.NumField() == 0 {
		t.Fatal("Deps has no neutral planes")
	}
	for i := 0; i < depsType.NumField(); i++ {
		field := depsType.Field(i)
		if field.Type.Kind() != reflect.Interface {
			t.Fatalf("Deps.%s type = %s, want interface", field.Name, field.Type)
		}
	}
}

func TestFlowAuthoringContractsUseTransitionalToolingCarriersWithoutWireTags(t *testing.T) {
	for _, contractType := range []reflect.Type{
		reflect.TypeOf((*FlowInterviews)(nil)).Elem(),
		reflect.TypeOf((*FlowAuthoring)(nil)).Elem(),
	} {
		for i := 0; i < contractType.NumMethod(); i++ {
			method := contractType.Method(i)
			if strings.Contains(method.Type.String(), "gin.") || strings.Contains(method.Type.String(), "http.") {
				t.Fatalf("%s.%s leaks transport type in %s", contractType.Name(), method.Name, method.Type)
			}
		}
	}
	for _, typ := range []reflect.Type{
		reflect.TypeOf(FlowInterviewRecord{}),
		reflect.TypeOf(FlowDefinitionRecord{}),
		reflect.TypeOf(FlowDefinitionWrite{}),
		reflect.TypeOf(FlowDefinitionCreateResult{}),
		reflect.TypeOf(FlowAdminDefinitionPatchIntent{}),
		reflect.TypeOf(FlowVersionRecord{}),
		reflect.TypeOf(FlowVersionWrite{}),
		reflect.TypeOf(FlowVersionCreateResult{}),
		reflect.TypeOf(FlowAdminVersionPatchIntent{}),
		reflect.TypeOf(FlowAuthoringError{}),
	} {
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			if field.Tag.Get("json") != "" {
				t.Fatalf("%s.%s has wire JSON tag %q", typ.Name(), field.Name, field.Tag.Get("json"))
			}
		}
	}
}

func TestBulkIngestContractUsesNeutralCarriersWithoutWireTags(t *testing.T) {
	bulkType := reflect.TypeOf((*BulkIngest)(nil)).Elem()
	for i := 0; i < bulkType.NumMethod(); i++ {
		method := bulkType.Method(i)
		if strings.Contains(method.Type.String(), "gin.") || strings.Contains(method.Type.String(), "http.") {
			t.Fatalf("BulkIngest.%s leaks transport type in %s", method.Name, method.Type)
		}
	}
	for _, typ := range []reflect.Type{
		reflect.TypeOf(BulkIngestCreateRequest{}),
		reflect.TypeOf(BulkIngestUploadRequest{}),
		reflect.TypeOf(BulkIngestError{}),
	} {
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			if field.Tag.Get("json") != "" {
				t.Fatalf("%s.%s has wire JSON tag %q", typ.Name(), field.Name, field.Tag.Get("json"))
			}
		}
	}
}

func TestStandardActionDescriptorCarriesNeutralHeaderFallbackEligibility(t *testing.T) {
	descType := reflect.TypeOf(StandardActionDescriptor{})
	field, ok := descType.FieldByName("HeaderFallbackEligible")
	if !ok {
		t.Fatal("StandardActionDescriptor.HeaderFallbackEligible missing")
	}
	if field.Type.Kind() != reflect.Bool {
		t.Fatalf("HeaderFallbackEligible type = %s, want bool", field.Type)
	}
	if tag := field.Tag.Get("json"); tag != "" {
		t.Fatalf("HeaderFallbackEligible has wire JSON tag %q; dialect mappers own wire tags", tag)
	}
}

func TestMetadataDeployContractUsesNeutralCarriersWithoutWireTags(t *testing.T) {
	deployType := reflect.TypeOf((*MetadataDeploy)(nil)).Elem()
	for i := 0; i < deployType.NumMethod(); i++ {
		method := deployType.Method(i)
		if strings.Contains(method.Type.String(), "gin.") || strings.Contains(method.Type.String(), "http.") {
			t.Fatalf("MetadataDeploy.%s leaks transport type in %s", method.Name, method.Type)
		}
	}
	for _, typ := range []reflect.Type{
		reflect.TypeOf(MetadataDeployResult{}),
		reflect.TypeOf(MetadataDeployComponentFailure{}),
		reflect.TypeOf(MetadataDeployRecordRequest{}),
		reflect.TypeOf(MetadataDeployRecordResult{}),
		reflect.TypeOf(MetadataDeployDiagnostic{}),
	} {
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			if field.Tag.Get("json") != "" {
				t.Fatalf("%s.%s has wire JSON tag %q", typ.Name(), field.Name, field.Tag.Get("json"))
			}
		}
	}
}

func TestApprovalContractUsesNeutralCarriersWithoutWireTags(t *testing.T) {
	approvalType := reflect.TypeOf((*Approvals)(nil)).Elem()
	for i := 0; i < approvalType.NumMethod(); i++ {
		method := approvalType.Method(i)
		if strings.Contains(method.Type.String(), "automation.") || strings.Contains(method.Type.String(), "models.SObject") {
			t.Fatalf("Approvals.%s exposes backend or SObject carrier: %s", method.Name, method.Type)
		}
	}
	for _, typ := range []reflect.Type{
		reflect.TypeOf(ApprovalSubmitRequest{}),
		reflect.TypeOf(ApprovalPendingView{}),
		reflect.TypeOf(ApprovalProcessInstancePendingWorkItem{}),
		reflect.TypeOf(ApprovalProcessInstanceSummary{}),
		reflect.TypeOf(ApprovalStepDisplayMetadata{}),
		reflect.TypeOf(ApprovalProcessActionFailure{}),
	} {
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			if field.Tag.Get("json") != "" {
				t.Fatalf("%s.%s has wire JSON tag %q", typ.Name(), field.Name, field.Tag.Get("json"))
			}
		}
	}
}

func TestDepsDoesNotCarryLifecycleRegistration(t *testing.T) {
	if _, ok := reflect.TypeOf(Deps{}).FieldByName("RecordLifecycle"); ok {
		t.Fatal("Deps.RecordLifecycle exists; lifecycle registration must stay a startup-only composition seam, not handler deps")
	}
}

func TestStandardActionPreviewUsesNeutralRecordCarrier(t *testing.T) {
	recordType := reflect.TypeOf(Record{})
	imageType := reflect.TypeOf(StandardActionCreatePreviewImage{})
	fields, ok := imageType.FieldByName("Fields")
	if !ok {
		t.Fatal("StandardActionCreatePreviewImage.Fields missing")
	}
	if fields.Type != recordType {
		t.Fatalf("Fields type = %s, want %s", fields.Type, recordType)
	}
	if fields.Tag.Get("json") != "" {
		t.Fatalf("Fields has wire JSON tag %q", fields.Tag.Get("json"))
	}

	childType := reflect.TypeOf(StandardActionChildPreview{})
	rows, ok := childType.FieldByName("Rows")
	if !ok {
		t.Fatal("StandardActionChildPreview.Rows missing")
	}
	if want := reflect.SliceOf(recordType); rows.Type != want {
		t.Fatalf("Rows type = %s, want %s", rows.Type, want)
	}
	if rows.Tag.Get("json") != "" {
		t.Fatalf("Rows has wire JSON tag %q", rows.Tag.Get("json"))
	}
}

func TestStandardActionResultsUseNeutralErrorsWithoutWireTags(t *testing.T) {
	errorType := reflect.TypeOf(StandardActionError{})
	if _, ok := errorType.FieldByName("StatusCode"); ok {
		t.Fatal("StandardActionError must not carry v63 statusCode-shaped field names")
	}
	if _, ok := errorType.FieldByName("Code"); !ok {
		t.Fatal("StandardActionError.Code missing")
	}
	for i := 0; i < errorType.NumField(); i++ {
		field := errorType.Field(i)
		if field.Tag.Get("json") != "" {
			t.Fatalf("StandardActionError.%s has wire JSON tag %q", field.Name, field.Tag.Get("json"))
		}
	}

	errorSlice := reflect.SliceOf(errorType)
	for _, typ := range []reflect.Type{
		reflect.TypeOf(StandardActionInvocationResult{}),
		reflect.TypeOf(StandardActionPreviewResult{}),
		reflect.TypeOf(StandardActionOutputPreview{}),
	} {
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			if field.Tag.Get("json") != "" {
				t.Fatalf("%s.%s has wire JSON tag %q", typ.Name(), field.Name, field.Tag.Get("json"))
			}
		}
		errors, ok := typ.FieldByName("Errors")
		if !ok {
			t.Fatalf("%s.Errors missing", typ.Name())
		}
		if errors.Type != errorSlice {
			t.Fatalf("%s.Errors type = %s, want %s", typ.Name(), errors.Type, errorSlice)
		}
	}
}

func TestSearchResultHasNoWireJSONTags(t *testing.T) {
	resultType := reflect.TypeOf(SearchResult{})
	for i := 0; i < resultType.NumField(); i++ {
		field := resultType.Field(i)
		if tag := field.Tag.Get("json"); tag != "" {
			t.Fatalf("SearchResult.%s has json tag %q; dialect mappers own wire tags", field.Name, tag)
		}
	}
}
