package catalogsnapshot

import (
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"
)

// CommandRegistrationIRV1 is the app-neutral AppRelease component grammar
// consumed by Catalog. L1 chooses which registrations to publish; Runtime only
// admits the exact projected kind/version pair.
const CommandRegistrationIRV1 = "nexuscrm.runtime-command-registration/v1"

var commandKindPattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_.:/-]*$`)

type commandRegistrationRecord struct {
	ComponentID          string                    `json:"componentId"`
	DefinitionVersion    DefinitionVersion         `json:"definitionVersion"`
	ContentDigest        Digest                    `json:"contentDigest"`
	CommandKind          string                    `json:"commandKind"`
	PayloadFormatVersion uint32                    `json:"payloadFormatVersion"`
	ExecutionBindings    []CommandExecutionBinding `json:"executionBindings,omitempty"`
}

func inputHasCommandAcceptanceProjection(values []RuntimeProjectionInput) bool {
	for _, value := range values {
		if value.Profile == ProjectionCommandAcceptanceV1 {
			return true
		}
	}
	return false
}

func inputHasCommandExecutionBindings(values []CommandRegistrationInput) bool {
	for _, value := range values {
		if len(value.ExecutionBindings) != 0 {
			return true
		}
	}
	return false
}

func commandRecordsHaveExecutionBindings(values []commandRegistrationRecord) bool {
	for _, value := range values {
		if len(value.ExecutionBindings) != 0 {
			return true
		}
	}
	return false
}

func buildCommandRegistrations(
	revision CatalogRevision,
	inputs []CommandRegistrationInput,
) ([]commandRegistrationRecord, error) {
	result := make([]commandRegistrationRecord, 0, len(inputs))
	components := make(map[string]struct{}, len(inputs))
	pairs := make(map[string]struct{}, len(inputs))
	for i, input := range inputs {
		path := fmt.Sprintf("commandRegistrations[%d]", i)
		if err := requireSourceRevision(path, revision, input.CatalogRevision); err != nil {
			return nil, err
		}
		record := commandRegistrationRecord{
			ComponentID: input.ComponentID, DefinitionVersion: input.DefinitionVersion,
			ContentDigest: input.ContentDigest, CommandKind: input.CommandKind,
			PayloadFormatVersion: input.PayloadFormatVersion,
			ExecutionBindings: append(
				[]CommandExecutionBinding(nil),
				input.ExecutionBindings...,
			),
		}
		sort.Slice(record.ExecutionBindings, func(i, j int) bool {
			return commandExecutionBindingKey(record.ExecutionBindings[i]) <
				commandExecutionBindingKey(record.ExecutionBindings[j])
		})
		if err := validateCommandRegistrationRecord(path, record); err != nil {
			return nil, err
		}
		componentKey := foldIdentity(record.ComponentID)
		if _, duplicate := components[componentKey]; duplicate {
			return nil, conflict(ConflictInvalidDefinition, path+".componentId", "command registration component %q is duplicated case-insensitively", record.ComponentID)
		}
		components[componentKey] = struct{}{}
		pairKey := commandRegistrationKey(record.CommandKind, record.PayloadFormatVersion)
		if _, duplicate := pairs[pairKey]; duplicate {
			return nil, conflict(ConflictInvalidDefinition, path, "command registration %q version %d is duplicated", record.CommandKind, record.PayloadFormatVersion)
		}
		pairs[pairKey] = struct{}{}
		result = append(result, record)
	}
	sort.Slice(result, func(i, j int) bool {
		left := commandRegistrationKey(result[i].CommandKind, result[i].PayloadFormatVersion)
		right := commandRegistrationKey(result[j].CommandKind, result[j].PayloadFormatVersion)
		if left == right {
			return identityLess(result[i].ComponentID, result[j].ComponentID)
		}
		return left < right
	})
	return result, nil
}

func validateCommandRegistrationRecord(path string, record commandRegistrationRecord) error {
	if err := requireCanonicalIdentity(path+".componentId", record.ComponentID); err != nil {
		return err
	}
	if strings.TrimSpace(string(record.DefinitionVersion)) == "" ||
		strings.TrimSpace(string(record.DefinitionVersion)) != string(record.DefinitionVersion) {
		return fmt.Errorf("%s has an invalid definition version", path)
	}
	if !validDigest(record.ContentDigest) {
		return fmt.Errorf("%s has an invalid content digest", path)
	}
	if len(record.CommandKind) > 255 || !commandKindPattern.MatchString(record.CommandKind) {
		return fmt.Errorf("%s has an invalid command kind %q", path, record.CommandKind)
	}
	if record.PayloadFormatVersion == 0 {
		return fmt.Errorf("%s has an unversioned payload format", path)
	}
	seen := make(map[string]struct{}, len(record.ExecutionBindings))
	for index, binding := range record.ExecutionBindings {
		bindingPath := fmt.Sprintf("%s.executionBindings[%d]", path, index)
		for name, value := range map[string]string{
			"authorizedWorkloadId": binding.AuthorizedWorkloadID,
			"definitionRef":        binding.DefinitionRef,
			"definitionVersion":    binding.DefinitionVersion,
			"contextNamespace":     binding.ContextNamespace,
		} {
			if strings.TrimSpace(value) == "" ||
				strings.TrimSpace(value) != value ||
				len(value) > 255 ||
				!utf8.ValidString(value) ||
				strings.IndexFunc(value, unicode.IsControl) >= 0 {
				return fmt.Errorf("%s.%s is blank or non-canonical", bindingPath, name)
			}
		}
		key := commandExecutionSelectorKey(
			record.CommandKind,
			record.PayloadFormatVersion,
			binding,
		)
		if _, duplicate := seen[key]; duplicate {
			return fmt.Errorf("%s is duplicated", bindingPath)
		}
		seen[key] = struct{}{}
	}
	return nil
}

func commandRegistrationKey(kind string, version uint32) string {
	return kind + "\x00" + strconv.FormatUint(uint64(version), 10)
}

func commandExecutionBindingKey(binding CommandExecutionBinding) string {
	return framedCommandExecutionIdentity(
		binding.AuthorizedWorkloadID,
		binding.DefinitionRef,
		binding.DefinitionVersion,
		binding.ContextNamespace,
	)
}

func commandExecutionSelectorKey(
	kind string,
	version uint32,
	binding CommandExecutionBinding,
) string {
	return framedCommandExecutionIdentity(
		kind,
		strconv.FormatUint(uint64(version), 10),
		binding.AuthorizedWorkloadID,
		binding.DefinitionRef,
		binding.DefinitionVersion,
	)
}

func framedCommandExecutionIdentity(parts ...string) string {
	var result strings.Builder
	for _, part := range parts {
		result.WriteString(strconv.Itoa(len(part)))
		result.WriteByte(':')
		result.WriteString(part)
	}
	return result.String()
}

// AllowsCommand reports whether this exact immutable view authorizes the
// command kind/payload-version pair. Missing views and absent registrations
// fail closed.
func (v *RuntimeMetadataView) AllowsCommand(kind string, version uint32) bool {
	if v == nil || v.commandRegistrations == nil {
		return false
	}
	_, ok := v.commandRegistrations[commandRegistrationKey(kind, version)]
	return ok
}

// CommandExecutionContext returns the Catalog-authorized context namespace for
// one workload and exact definition. The namespace is authority, not
// caller-authored command payload. Missing N-1 bindings fail closed for
// definition-selecting executors while ordinary kind/version-only commands
// remain unaffected.
func (v *RuntimeMetadataView) CommandExecutionContext(
	kind string,
	version uint32,
	authorizedWorkloadID string,
	definitionRef string,
	definitionVersion string,
) (string, bool) {
	if v == nil || v.commandExecutionBindings == nil {
		return "", false
	}
	namespace, ok := v.commandExecutionBindings[commandExecutionSelectorKey(
		kind,
		version,
		CommandExecutionBinding{
			AuthorizedWorkloadID: authorizedWorkloadID,
			DefinitionRef:        definitionRef,
			DefinitionVersion:    definitionVersion,
		},
	)]
	return namespace, ok
}
