package layoutroutingsecurityv1

import (
	"encoding/hex"
	"fmt"
	"strings"
	"unicode/utf8"
)

// ValidateBinding fails closed unless every member of the independently
// attested routing-security identity is present and canonical.
func ValidateBinding(binding Binding) error {
	if err := validateOrgID(binding.OrgID); err != nil {
		return fmt.Errorf("LayoutRoutingSecurityBinding.OrgID: %w", err)
	}
	if binding.SecurityEpoch == 0 {
		return fmt.Errorf("LayoutRoutingSecurityBinding.SecurityEpoch must be positive")
	}
	if err := validateDigest(binding.LayoutRoutingSecurityViewDigest); err != nil {
		return fmt.Errorf("LayoutRoutingSecurityBinding.LayoutRoutingSecurityViewDigest: %w", err)
	}
	return nil
}

// ValidateProfileKey validates one canonical namespaced Profile key without
// normalizing or rewriting caller input.
func ValidateProfileKey(key ProfileKey) error {
	return validateStableKey("ProfileKey", key.NamespaceID, key.LocalKey)
}

// ValidateRoleKey validates one canonical namespaced Role key without
// normalizing or rewriting caller input.
func ValidateRoleKey(key RoleKey) error {
	return validateStableKey("RoleKey", key.NamespaceID, key.LocalKey)
}

// ValidateOrgID validates one opaque org identifier without normalization or
// database-collation semantics. It is shared by routing-security artifacts
// that must bind an exact org before an owning authority admits them.
func ValidateOrgID(orgID string) error {
	return validateOrgID(orgID)
}

// ValidateResolvedLocalID validates one opaque target-local Profile or Role
// identifier. A caller may use the same exact bytes once in each separate
// typed dimension, but never relies on normalized or collation-based identity.
func ValidateResolvedLocalID(localID string) error {
	return validateResolvedID(localID)
}

// ValidateView validates the complete identity projection and active Role
// forest. Input order is not semantic and need not already be canonical.
func ValidateView(view View) error {
	if err := validateOrgID(view.OrgID); err != nil {
		return fmt.Errorf("LayoutRoutingSecurityView.OrgID: %w", err)
	}
	if view.CanonicalizationVersion != ViewCanonicalizationVersion {
		return fmt.Errorf("LayoutRoutingSecurityView.CanonicalizationVersion: unsupported value %q", view.CanonicalizationVersion)
	}
	if view.KeyGrammarVersion != KeyGrammarVersion {
		return fmt.Errorf("LayoutRoutingSecurityView.KeyGrammarVersion: unsupported value %q", view.KeyGrammarVersion)
	}
	if view.RoleTopologyGrammarVersion != RoleTopologyGrammarVersion {
		return fmt.Errorf("LayoutRoutingSecurityView.RoleTopologyGrammarVersion: unsupported value %q", view.RoleTopologyGrammarVersion)
	}
	if view.Profiles == nil {
		return fmt.Errorf("LayoutRoutingSecurityView.Profiles must be present")
	}
	if view.Roles == nil {
		return fmt.Errorf("LayoutRoutingSecurityView.Roles must be present")
	}
	if len(view.Profiles) > MaxProfileIdentities {
		return fmt.Errorf("LayoutRoutingSecurityView.Profiles exceeds %d entries", MaxProfileIdentities)
	}
	if len(view.Roles) > MaxRoleIdentities {
		return fmt.Errorf("LayoutRoutingSecurityView.Roles exceeds %d entries", MaxRoleIdentities)
	}

	profileKeys := make(map[string]struct{}, len(view.Profiles))
	profileIDs := make(map[string]struct{}, len(view.Profiles))
	for index, identity := range view.Profiles {
		if err := ValidateProfileKey(identity.Key); err != nil {
			return fmt.Errorf("LayoutRoutingSecurityView.Profiles[%d]: %w", index, err)
		}
		if err := validateResolvedID(identity.ResolvedProfileID); err != nil {
			return fmt.Errorf("LayoutRoutingSecurityView.Profiles[%d].ResolvedProfileID: %w", index, err)
		}
		if err := validateIdentityState(identity.State); err != nil {
			return fmt.Errorf("LayoutRoutingSecurityView.Profiles[%d].State: %w", index, err)
		}
		key := stableKey(identity.Key.NamespaceID, identity.Key.LocalKey)
		if _, duplicate := profileKeys[key]; duplicate {
			return fmt.Errorf("LayoutRoutingSecurityView.Profiles contains duplicate ProfileKey")
		}
		profileKeys[key] = struct{}{}
		if _, duplicate := profileIDs[identity.ResolvedProfileID]; duplicate {
			return fmt.Errorf("LayoutRoutingSecurityView.Profiles contains duplicate ResolvedProfileID %q", identity.ResolvedProfileID)
		}
		profileIDs[identity.ResolvedProfileID] = struct{}{}
	}

	roleByKey := make(map[string]RoleIdentity, len(view.Roles))
	roleIDs := make(map[string]struct{}, len(view.Roles))
	for index, identity := range view.Roles {
		if err := ValidateRoleKey(identity.Key); err != nil {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles[%d]: %w", index, err)
		}
		if err := validateResolvedID(identity.ResolvedRoleID); err != nil {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles[%d].ResolvedRoleID: %w", index, err)
		}
		if err := validateIdentityState(identity.State); err != nil {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles[%d].State: %w", index, err)
		}
		if identity.State == IdentityStateTombstone && identity.ParentRoleKey != nil {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles[%d]: tombstone cannot have ParentRoleKey", index)
		}
		if identity.ParentRoleKey != nil {
			if err := ValidateRoleKey(*identity.ParentRoleKey); err != nil {
				return fmt.Errorf("LayoutRoutingSecurityView.Roles[%d].ParentRoleKey: %w", index, err)
			}
		}
		key := stableKey(identity.Key.NamespaceID, identity.Key.LocalKey)
		if _, duplicate := roleByKey[key]; duplicate {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles contains duplicate RoleKey")
		}
		roleByKey[key] = identity
		if _, duplicate := roleIDs[identity.ResolvedRoleID]; duplicate {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles contains duplicate ResolvedRoleID %q", identity.ResolvedRoleID)
		}
		roleIDs[identity.ResolvedRoleID] = struct{}{}
	}

	parents := make(map[string]string, len(view.Roles))
	for key, identity := range roleByKey {
		if identity.ParentRoleKey == nil {
			continue
		}
		parentKey := stableKey(identity.ParentRoleKey.NamespaceID, identity.ParentRoleKey.LocalKey)
		if parentKey == key {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles: RoleKey cannot parent itself")
		}
		parent, exists := roleByKey[parentKey]
		if !exists {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles: ParentRoleKey does not exist")
		}
		if parent.State != IdentityStateActive {
			return fmt.Errorf("LayoutRoutingSecurityView.Roles: active Role cannot name a tombstoned parent")
		}
		parents[key] = parentKey
	}
	if err := rejectRoleCycles(roleByKey, parents); err != nil {
		return err
	}
	if size := canonicalViewSize(view); size > MaxCanonicalViewBytes {
		return fmt.Errorf("LayoutRoutingSecurityView canonical bytes exceed %d", MaxCanonicalViewBytes)
	}
	return nil
}

func validateStableKey(label, namespaceID, localKey string) error {
	if err := validateDottedID(label+".NamespaceID", namespaceID, MaxNamespaceIDBytes, 2); err != nil {
		return err
	}
	if err := validateDottedID(label+".LocalKey", localKey, MaxLocalKeyBytes, 1); err != nil {
		return err
	}
	return nil
}

func validateDottedID(label, value string, maximumBytes, minimumSegments int) error {
	if value == "" || len(value) > maximumBytes {
		return fmt.Errorf("%s must be 1..%d lowercase ASCII bytes", label, maximumBytes)
	}
	segments := strings.Split(value, ".")
	if len(segments) < minimumSegments {
		return fmt.Errorf("%s requires at least %d dot-separated segments", label, minimumSegments)
	}
	for _, segment := range segments {
		if len(segment) == 0 || len(segment) > MaxKeySegmentBytes || segment[0] < 'a' || segment[0] > 'z' || segment[len(segment)-1] == '-' {
			return fmt.Errorf("%s contains invalid canonical segment %q", label, segment)
		}
		for _, char := range []byte(segment[1:]) {
			if (char < 'a' || char > 'z') && (char < '0' || char > '9') && char != '-' {
				return fmt.Errorf("%s contains invalid canonical segment %q", label, segment)
			}
		}
	}
	return nil
}

func validateResolvedID(value string) error {
	if value == "" || len(value) > MaxResolvedIDBytes || !utf8.ValidString(value) {
		return fmt.Errorf("must be well-formed UTF-8 with 1..%d bytes", MaxResolvedIDBytes)
	}
	if value[0] == ' ' || value[len(value)-1] == ' ' {
		return fmt.Errorf("must not have surrounding ASCII space")
	}
	for _, b := range []byte(value) {
		if b < 0x20 || b == 0x7f {
			return fmt.Errorf("must not contain ASCII control or DEL bytes")
		}
	}
	return nil
}

func validateIdentityState(value IdentityState) error {
	switch value {
	case IdentityStateActive, IdentityStateTombstone:
		return nil
	default:
		return fmt.Errorf("must be ACTIVE or TOMBSTONE")
	}
}

func rejectRoleCycles(roles map[string]RoleIdentity, parents map[string]string) error {
	const (
		visiting = iota + 1
		done
	)
	states := make(map[string]int, len(roles))
	for start := range roles {
		if states[start] == done {
			continue
		}
		path := make([]string, 0, 8)
		key := start
		for {
			switch states[key] {
			case visiting:
				return fmt.Errorf("LayoutRoutingSecurityView.Roles contains a parent cycle")
			case done:
				for _, visited := range path {
					states[visited] = done
				}
				key = ""
			}
			if key == "" {
				break
			}
			states[key] = visiting
			path = append(path, key)
			parent, exists := parents[key]
			if !exists {
				for _, visited := range path {
					states[visited] = done
				}
				break
			}
			key = parent
		}
	}
	return nil
}

func stableKey(namespaceID, localKey string) string {
	return namespaceID + "\x00" + localKey
}

func validateOrgID(value string) error {
	if value == "" || len(value) > MaxOrgIDBytes || !utf8.ValidString(value) {
		return fmt.Errorf("must be well-formed UTF-8 with 1..%d bytes", MaxOrgIDBytes)
	}
	if value[0] == ' ' || value[len(value)-1] == ' ' {
		return fmt.Errorf("must not have surrounding ASCII space")
	}
	for _, b := range []byte(value) {
		if b < 0x20 || b == 0x7f {
			return fmt.Errorf("must not contain ASCII control or DEL bytes")
		}
	}
	return nil
}

func validateDigest(value string) error {
	const prefix = "sha256:"
	if len(value) != len(prefix)+64 || !strings.HasPrefix(value, prefix) {
		return fmt.Errorf("must be sha256:<64 lowercase hex>")
	}
	hexText := value[len(prefix):]
	if strings.ToLower(hexText) != hexText {
		return fmt.Errorf("hex must be lowercase")
	}
	if _, err := hex.DecodeString(hexText); err != nil {
		return fmt.Errorf("hex is invalid")
	}
	return nil
}
