package metadata

import (
	"context"
	"errors"
	"strings"
	"testing"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestObjectKeyV1ValidateObjectMetadataBoundaries(t *testing.T) {
	validationSvc := NewValidationService(nil)

	for _, tc := range objectKeyV1ValidCases() {
		t.Run(tc.name, func(t *testing.T) {
			err := validationSvc.ValidateObjectMetadata(&models.ObjectMetadata{APIName: tc.apiName})
			if err != nil {
				t.Fatalf("ValidateObjectMetadata(%q): %v", tc.apiName, err)
			}
		})
	}

	for _, tc := range objectKeyV1InvalidCases() {
		t.Run(tc.name, func(t *testing.T) {
			err := validationSvc.ValidateObjectMetadata(&models.ObjectMetadata{APIName: tc.apiName})
			assertObjectKeyV1ValidationError(t, err, "ValidateObjectMetadata", tc.apiName)
		})
	}
}

func TestObjectKeyV1PrepareTableDefinitionBoundaries(t *testing.T) {
	for _, tc := range objectKeyV1ValidCases() {
		t.Run(tc.name, func(t *testing.T) {
			service := NewMetadataService(
				nil,
				NewSchemaManager(persistence.NewSchemaRepository(nil, nil)),
				nil,
			)
			definition, _, err := service.PrepareTableDefinition(context.Background(), &models.ObjectMetadata{
				APIName: tc.apiName,
				Label:   "Object key boundary",
			})
			if err != nil {
				t.Fatalf("PrepareTableDefinition(%q): %v", tc.apiName, err)
			}
			if definition.TableName != tc.apiName {
				t.Fatalf("PrepareTableDefinition(%q) table name = %q, want exact source name", tc.apiName, definition.TableName)
			}
		})
	}

	for _, tc := range objectKeyV1InvalidCases() {
		t.Run(tc.name, func(t *testing.T) {
			service := NewMetadataService(nil, nil, nil)
			_, _, err := service.PrepareTableDefinition(context.Background(), &models.ObjectMetadata{
				APIName: tc.apiName,
				Label:   "Object key boundary",
			})
			assertObjectKeyV1ValidationError(t, err, "PrepareTableDefinition", tc.apiName)
		})
	}
}

func TestInteractiveObjectIncarnationIsFreshAndIgnoresCallerIdentity(t *testing.T) {
	first := &models.ObjectMetadata{ID: "caller-controlled", APIName: "Replaceable__c"}
	second := &models.ObjectMetadata{ID: "caller-controlled", APIName: "Replaceable__c"}
	mintInteractiveObjectIncarnation(first)
	mintInteractiveObjectIncarnation(second)
	if first.ID == "" || second.ID == "" || first.ID == second.ID {
		t.Fatalf("object incarnation IDs = %q / %q, want distinct non-empty identities", first.ID, second.ID)
	}
	if first.ID == "caller-controlled" || second.ID == "caller-controlled" {
		t.Fatalf("interactive object owner trusted caller identity: %q / %q", first.ID, second.ID)
	}
}

func TestObjectKeyV1CreatePathsRejectInvalidNames(t *testing.T) {
	createPaths := []struct {
		name   string
		create func(*MetadataService, *models.ObjectMetadata) error
	}{
		{
			name: "CreateSchema",
			create: func(service *MetadataService, schema *models.ObjectMetadata) error {
				return service.CreateSchema(context.Background(), schema)
			},
		},
		{
			name: "CreateSchemaOptimized",
			create: func(service *MetadataService, schema *models.ObjectMetadata) error {
				return service.CreateSchemaOptimized(context.Background(), schema)
			},
		},
	}

	for _, path := range createPaths {
		t.Run(path.name, func(t *testing.T) {
			for _, tc := range objectKeyV1InvalidCases() {
				t.Run(tc.name, func(t *testing.T) {
					// Keep the optional validation service unset so this also proves
					// each create path reaches the mandatory preparation guard before
					// it can touch repository or schema-manager dependencies.
					service := NewMetadataService(nil, nil, nil)
					err := path.create(service, &models.ObjectMetadata{
						APIName: tc.apiName,
						Label:   "Object key boundary",
					})
					assertObjectKeyV1ValidationError(t, err, path.name, tc.apiName)
				})
			}
		})
	}
}

func objectKeyV1ValidCases() []struct {
	name    string
	apiName string
} {
	return []struct {
		name    string
		apiName string
	}{
		{name: "one byte", apiName: "A"},
		{name: "64 bytes", apiName: strings.Repeat("A", 64)},
	}
}

func objectKeyV1InvalidCases() []struct {
	name    string
	apiName string
} {
	return []struct {
		name    string
		apiName string
	}{
		{name: "65 bytes", apiName: strings.Repeat("A", 65)},
		{name: "non ASCII", apiName: "Café"},
		{name: "padded", apiName: " Widget__c "},
		{name: "invalid grammar", apiName: "9Widget__c"},
	}
}

func assertObjectKeyV1ValidationError(t *testing.T, err error, operation, apiName string) {
	t.Helper()

	if err == nil {
		t.Fatalf("%s(%q) = nil, want validation error", operation, apiName)
	}
	var validationErr *apperrors.ValidationError
	if !errors.As(err, &validationErr) {
		t.Fatalf("%s(%q) error = %T, want *ValidationError: %v", operation, apiName, err, err)
	}
}
