package layoutroutingidentitymappingv1

import (
	"fmt"

	routing "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutroutingsecurityv1"
)

// ValidateProposal validates only canonical proposal content. It does not
// establish source authorization or completeness against live rows.
func ValidateProposal(proposal Proposal) error {
	if err := routing.ValidateOrgID(proposal.OrgID); err != nil {
		return fmt.Errorf("LayoutRoutingIdentityMappingProposal.OrgID: %w", err)
	}
	if proposal.CanonicalizationVersion != CanonicalizationVersion {
		return fmt.Errorf(
			"LayoutRoutingIdentityMappingProposal.CanonicalizationVersion: unsupported value %q",
			proposal.CanonicalizationVersion,
		)
	}
	if proposal.KeyGrammarVersion != routing.KeyGrammarVersion {
		return fmt.Errorf(
			"LayoutRoutingIdentityMappingProposal.KeyGrammarVersion: unsupported value %q",
			proposal.KeyGrammarVersion,
		)
	}
	if proposal.Profiles == nil {
		return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Profiles must be present")
	}
	if proposal.Roles == nil {
		return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Roles must be present")
	}
	if len(proposal.Profiles) > MaxProfileMappings {
		return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Profiles exceeds %d entries", MaxProfileMappings)
	}
	if len(proposal.Roles) > MaxRoleMappings {
		return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Roles exceeds %d entries", MaxRoleMappings)
	}

	profileKeys := make(map[string]struct{}, len(proposal.Profiles))
	profileIDs := make(map[string]struct{}, len(proposal.Profiles))
	for index, mapping := range proposal.Profiles {
		if err := routing.ValidateProfileKey(mapping.Key); err != nil {
			return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Profiles[%d]: %w", index, err)
		}
		if err := routing.ValidateResolvedLocalID(mapping.ResolvedProfileID); err != nil {
			return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Profiles[%d].ResolvedProfileID: %w", index, err)
		}
		key := stableKey(mapping.Key.NamespaceID, mapping.Key.LocalKey)
		if _, exists := profileKeys[key]; exists {
			return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Profiles contains duplicate ProfileKey")
		}
		profileKeys[key] = struct{}{}
		if _, exists := profileIDs[mapping.ResolvedProfileID]; exists {
			return fmt.Errorf(
				"LayoutRoutingIdentityMappingProposal.Profiles contains duplicate ResolvedProfileID %q",
				mapping.ResolvedProfileID,
			)
		}
		profileIDs[mapping.ResolvedProfileID] = struct{}{}
	}

	roleKeys := make(map[string]struct{}, len(proposal.Roles))
	roleIDs := make(map[string]struct{}, len(proposal.Roles))
	for index, mapping := range proposal.Roles {
		if err := routing.ValidateRoleKey(mapping.Key); err != nil {
			return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Roles[%d]: %w", index, err)
		}
		if err := routing.ValidateResolvedLocalID(mapping.ResolvedRoleID); err != nil {
			return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Roles[%d].ResolvedRoleID: %w", index, err)
		}
		key := stableKey(mapping.Key.NamespaceID, mapping.Key.LocalKey)
		if _, exists := roleKeys[key]; exists {
			return fmt.Errorf("LayoutRoutingIdentityMappingProposal.Roles contains duplicate RoleKey")
		}
		roleKeys[key] = struct{}{}
		if _, exists := roleIDs[mapping.ResolvedRoleID]; exists {
			return fmt.Errorf(
				"LayoutRoutingIdentityMappingProposal.Roles contains duplicate ResolvedRoleID %q",
				mapping.ResolvedRoleID,
			)
		}
		roleIDs[mapping.ResolvedRoleID] = struct{}{}
	}
	if size := canonicalProposalSize(proposal); size > MaxCanonicalProposalBytes {
		return fmt.Errorf(
			"LayoutRoutingIdentityMappingProposal canonical bytes exceed %d",
			MaxCanonicalProposalBytes,
		)
	}
	return nil
}

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