package resourceaccessv1

import (
	"context"
	"encoding/json"
	"regexp"
	"sort"
	"strings"
	"unicode"
	"unicode/utf8"

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

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

func ValidateRequest(ctx context.Context, request Request) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	return validateRequestShape(request)
}

func validateRequestShape(request Request) error {
	if !request.Operation.Valid() {
		return invalid(common.InvalidComponentOperation)
	}
	if !identifier(request.ResourceType) || request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0 {
		return invalid(common.InvalidComponentCommandData)
	}
	if request.Operation.ReadOnly() && request.CommandID != "" || !request.Operation.ReadOnly() && !validCommandIdentifier(request.CommandID) {
		return invalid(common.InvalidComponentCommandID)
	}
	valid := false
	switch request.Operation {
	case ListGrants:
		valid = identifier(request.ResourceID) && request.GrantID == "" && request.Grant == nil && len(request.Grants) == 0 && request.AccessLevel == "" && request.PrincipalType == "" && request.SourceOwnerID == "" && request.TargetOwnerID == ""
	case GetGrant, DeleteGrant:
		valid = (request.ResourceID == "" || identifier(request.ResourceID)) && identifier(request.GrantID) && request.Grant == nil && len(request.Grants) == 0 && request.AccessLevel == "" && request.PrincipalType == "" && request.SourceOwnerID == "" && request.TargetOwnerID == ""
	case CreateGrant:
		valid = identifier(request.ResourceID) && request.GrantID == "" && request.AccessLevel == "" && request.PrincipalType == "" && request.SourceOwnerID == "" && request.TargetOwnerID == "" && (validGrant(request.Grant) && request.Grants == nil || request.Grant == nil && len(request.Grants) > 0 && len(request.Grants) <= MaxGrants)
		for i := range request.Grants {
			valid = valid && validGrant(&request.Grants[i])
		}
		valid = valid && uniqueGrantInputs(request.Grants)
	case EnsureGrant:
		valid = identifier(request.ResourceID) && request.GrantID == "" && validGrant(request.Grant) && len(request.Grants) == 0 && request.AccessLevel == "" && request.PrincipalType == "" && request.SourceOwnerID == "" && request.TargetOwnerID == ""
	case ReplaceGrants:
		valid = identifier(request.ResourceID) && request.GrantID == "" && request.Grant == nil && len(request.Grants) <= MaxGrants && request.AccessLevel == "" && request.PrincipalType == "" && request.SourceOwnerID == "" && request.TargetOwnerID == ""
		for i := range request.Grants {
			valid = valid && validGrant(&request.Grants[i])
		}
		valid = valid && uniqueGrantInputs(request.Grants)
	case UpdateGrant:
		valid = (request.ResourceID == "" || identifier(request.ResourceID)) && identifier(request.GrantID) && request.Grant == nil && len(request.Grants) == 0 && identifier(request.AccessLevel) && request.PrincipalType == "" && request.SourceOwnerID == "" && request.TargetOwnerID == ""
	case DeletePrincipalType:
		valid = identifier(request.ResourceID) && request.GrantID == "" && request.Grant == nil && len(request.Grants) == 0 && request.AccessLevel == "" && identifier(request.PrincipalType) && request.SourceOwnerID == "" && request.TargetOwnerID == ""
	case TransferOwnership:
		valid = request.ResourceID == "" && request.GrantID == "" && request.Grant == nil && len(request.Grants) == 0 && request.AccessLevel == "" && request.PrincipalType == "" && identifier(request.SourceOwnerID) && identifier(request.TargetOwnerID) && request.SourceOwnerID != request.TargetOwnerID
	}
	if !valid {
		return invalid(common.InvalidComponentCommandData)
	}
	encoded, _ := json.Marshal(request)
	if len(encoded) > MaxSemanticRequestBytes {
		return &common.Failure{Category: common.FailureResourceExhausted, LimitKind: common.LimitSemanticRequest, Maximum: MaxSemanticRequestBytes}
	}
	return nil
}

func ValidateResponse(request Request, response Response) error {
	// The response validator is the final correlation boundary on both sides of
	// the wire. Reject responses for request shapes that could not be admitted,
	// then bind every returned identity to the operation's request authority.
	if err := validateRequestShape(request); err != nil {
		return internal()
	}
	if !common.ValidReceipts(response.Receipts, CapabilityName) || request.MinCatalogRevision != nil && response.Receipts.CatalogRevision < *request.MinCatalogRevision {
		return internal()
	}
	if len(response.Grants) > MaxGrants ||
		(request.Operation.ReadOnly() && response.CommandID != "") ||
		(!request.Operation.ReadOnly() && response.CommandID != request.CommandID) ||
		(request.Operation != TransferOwnership && response.AffectedResources != 0) ||
		response.AffectedResources > MaxOwnershipTransferResources {
		return internal()
	}
	for i, grant := range response.Grants {
		if !identifier(grant.ID) || !identifier(grant.ResourceType) || !identifier(grant.ResourceID) || !identifier(grant.PrincipalID) || !identifier(grant.PrincipalType) || !identifier(grant.AccessLevel) || i > 0 && response.Grants[i-1].ID >= grant.ID {
			return internal()
		}
	}
	if !uniqueGrantPrincipals(response.Grants) {
		return internal()
	}
	if !responseMatchesRequest(request, response) {
		return internal()
	}
	encoded, _ := json.Marshal(response)
	if len(encoded) > MaxSemanticResponseBytes {
		return internal()
	}
	return nil
}

func responseMatchesRequest(request Request, response Response) bool {
	sameTarget := func(grant Grant, resourceID string) bool {
		return grant.ResourceType == request.ResourceType && grant.ResourceID == resourceID
	}
	switch request.Operation {
	case ListGrants:
		for _, grant := range response.Grants {
			if !sameTarget(grant, request.ResourceID) {
				return false
			}
		}
		return true
	case GetGrant:
		return len(response.Grants) == 1 && response.Grants[0].ID == request.GrantID && response.Grants[0].ResourceType == request.ResourceType &&
			(request.ResourceID == "" || response.Grants[0].ResourceID == request.ResourceID)
	case CreateGrant:
		inputs := request.Grants
		if request.Grant != nil {
			inputs = []GrantInput{*request.Grant}
		}
		return grantsMatchInputs(response.Grants, request.ResourceType, request.ResourceID, inputs)
	case EnsureGrant:
		return request.Grant != nil && grantsMatchInputs(response.Grants, request.ResourceType, request.ResourceID, []GrantInput{*request.Grant})
	case ReplaceGrants:
		return grantsMatchInputs(response.Grants, request.ResourceType, request.ResourceID, request.Grants)
	case UpdateGrant:
		return len(response.Grants) == 1 && response.Grants[0].ID == request.GrantID && response.Grants[0].ResourceType == request.ResourceType && response.Grants[0].AccessLevel == request.AccessLevel &&
			(request.ResourceID == "" || response.Grants[0].ResourceID == request.ResourceID)
	case DeleteGrant, DeletePrincipalType:
		return len(response.Grants) == 0
	case TransferOwnership:
		return len(response.Grants) == 0 && response.AffectedResources <= MaxOwnershipTransferResources
	default:
		return false
	}
}

func grantsMatchInputs(grants []Grant, resourceType, resourceID string, inputs []GrantInput) bool {
	if len(grants) != len(inputs) {
		return false
	}
	type inputKey struct {
		principalID   string
		principalType string
		accessLevel   string
	}
	want := make(map[inputKey]int, len(inputs))
	for _, input := range inputs {
		want[inputKey{input.PrincipalID, input.PrincipalType, input.AccessLevel}]++
	}
	for _, grant := range grants {
		if grant.ResourceType != resourceType || grant.ResourceID != resourceID {
			return false
		}
		key := inputKey{grant.PrincipalID, grant.PrincipalType, grant.AccessLevel}
		if want[key] == 0 {
			return false
		}
		want[key]--
	}
	return true
}

func SortResponse(response *Response) {
	if response == nil {
		return
	}
	sort.Slice(response.Grants, func(i, j int) bool { return response.Grants[i].ID < response.Grants[j].ID })
}
func validGrant(value *GrantInput) bool {
	return value != nil && identifier(value.PrincipalID) && identifier(value.PrincipalType) && identifier(value.AccessLevel)
}
func identifier(value string) bool {
	if value == "" || len(value) > MaxIdentifierBytes || !utf8.ValidString(value) || strings.TrimSpace(value) != value {
		return false
	}
	for _, r := range value {
		if unicode.IsControl(r) {
			return false
		}
	}
	return true
}

func validCommandIdentifier(value string) bool {
	return len(value) <= MaxCommandIdentifierBytes && utf8.ValidString(value) && commandIdentifierPattern.MatchString(value)
}

type typedPrincipalKey struct {
	principalID   string
	principalType string
}

func uniqueGrantInputs(values []GrantInput) bool {
	seen := make(map[typedPrincipalKey]struct{}, len(values))
	for _, value := range values {
		// A grant is identified by its typed principal. AccessLevel is mutable
		// state, not part of that identity; admitting two levels for the same
		// principal would defer an ambiguous command to the storage uniqueness
		// constraint instead of rejecting it at the protocol boundary.
		key := typedPrincipalKey{value.PrincipalID, value.PrincipalType}
		if _, exists := seen[key]; exists {
			return false
		}
		seen[key] = struct{}{}
	}
	return true
}

func uniqueGrantPrincipals(values []Grant) bool {
	seen := make(map[typedPrincipalKey]struct{}, len(values))
	for _, value := range values {
		key := typedPrincipalKey{value.PrincipalID, value.PrincipalType}
		if _, exists := seen[key]; exists {
			return false
		}
		seen[key] = struct{}{}
	}
	return true
}
func invalid(c common.InvalidComponent) *Failure {
	return &Failure{Category: common.FailureInvalidRequest, InvalidComponent: c}
}
func internal() *Failure { return &Failure{Category: common.FailureInternal} }
