package permissionadminv1

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

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

func ValidateReadRequest(ctx context.Context, request ReadRequest) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	if !validReadRequestShape(request) {
		return invalid(common.InvalidComponentCommandData)
	}
	return validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest)
}

func ValidateReadResponse(request ReadRequest, response ReadResponse) error {
	if !validReadRequestShape(request) || !common.ValidReceipts(response.Receipts, CapabilityName) ||
		(request.MinCatalogRevision != nil && response.Receipts.CatalogRevision < *request.MinCatalogRevision) ||
		!onlySelectedReadFamily(request.Selector, response) {
		return internal()
	}
	// Reject an oversized in-process response before catalog validation builds
	// indexes and before JSON serialization allocates a second copy. The final
	// encoded-size check remains authoritative for structural/escaping overhead.
	if !readResponseWithinRawBudget(response) {
		return internal()
	}
	switch request.Selector {
	case ReadSystemCatalog:
		if len(response.SystemCatalog) > MaxCatalogEntries || !validSystemCatalog(response.SystemCatalog) {
			return internal()
		}
	case ReadSystemGrants:
		if !validResponseGrants(response.SystemGrants) {
			return internal()
		}
	case ReadRecordTypeCatalog:
		if len(response.RecordTypeCatalog) > MaxCatalogEntries || !validRecordTypeCatalog(response.RecordTypeCatalog) {
			return internal()
		}
	case ReadRecordTypeGrants:
		if !validResponseGrants(response.RecordTypeGrants) {
			return internal()
		}
	case ReadCustomCatalog:
		if len(response.CustomCatalog) > MaxCatalogEntries || !validCustomCatalog(response.CustomCatalog) {
			return internal()
		}
	case ReadCustomGrants:
		if !validResponseGrants(response.CustomGrants) {
			return internal()
		}
	default:
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

func readResponseWithinRawBudget(response ReadResponse) bool {
	if len(response.SystemCatalog) > MaxCatalogEntries || len(response.RecordTypeCatalog) > MaxCatalogEntries ||
		len(response.CustomCatalog) > MaxCatalogEntries || len(response.SystemGrants) > MaxGrants ||
		len(response.RecordTypeGrants) > MaxGrants || len(response.CustomGrants) > MaxGrants {
		return false
	}
	remaining := MaxSemanticResponseBytes
	take := func(values ...string) bool {
		for _, value := range values {
			if len(value) > remaining {
				return false
			}
			remaining -= len(value)
		}
		return true
	}
	for _, value := range response.SystemCatalog {
		if !take(value.PermissionID, value.Label, value.Category, value.Description, value.RequiresPermissionID) {
			return false
		}
	}
	for _, value := range response.RecordTypeCatalog {
		if !take(value.RecordTypeID, value.ResourceType, value.StableName, value.Label) {
			return false
		}
	}
	for _, value := range response.CustomCatalog {
		if !take(value.PermissionID, value.StableName, value.Label) {
			return false
		}
	}
	for _, grants := range [][]Grant{response.SystemGrants, response.RecordTypeGrants, response.CustomGrants} {
		for _, value := range grants {
			if !take(value.PermissionID) {
				return false
			}
		}
	}
	return true
}

func ValidateMutateRequest(ctx context.Context, request MutateRequest) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	if !validMutateRequestShape(request) {
		return invalid(common.InvalidComponentCommandData)
	}
	return validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest)
}

func ValidateMutateResponse(request MutateRequest, response MutateResponse) error {
	if !validMutateRequestShape(request) || !common.ValidReceipts(response.Receipts, CapabilityName) ||
		(request.MinCatalogRevision != nil && response.Receipts.CatalogRevision < *request.MinCatalogRevision) {
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

func SortReadResponse(response *ReadResponse) {
	if response == nil {
		return
	}
	sort.Slice(response.SystemCatalog, func(i, j int) bool {
		return response.SystemCatalog[i].PermissionID < response.SystemCatalog[j].PermissionID
	})
	sort.Slice(response.SystemGrants, func(i, j int) bool {
		return response.SystemGrants[i].PermissionID < response.SystemGrants[j].PermissionID
	})
	sort.Slice(response.RecordTypeCatalog, func(i, j int) bool {
		return response.RecordTypeCatalog[i].RecordTypeID < response.RecordTypeCatalog[j].RecordTypeID
	})
	sort.Slice(response.RecordTypeGrants, func(i, j int) bool {
		return response.RecordTypeGrants[i].PermissionID < response.RecordTypeGrants[j].PermissionID
	})
	sort.Slice(response.CustomCatalog, func(i, j int) bool {
		return response.CustomCatalog[i].PermissionID < response.CustomCatalog[j].PermissionID
	})
	sort.Slice(response.CustomGrants, func(i, j int) bool {
		return response.CustomGrants[i].PermissionID < response.CustomGrants[j].PermissionID
	})
}

func validReadRequestShape(request ReadRequest) bool {
	if !request.Selector.Valid() || (request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0) {
		return false
	}
	if request.Selector.RequiresPermissionSet() {
		return validOpaque(request.PermissionSetID, MaxIdentifierBytes)
	}
	return request.PermissionSetID == ""
}

func validMutateRequestShape(request MutateRequest) bool {
	return request.Selector.Valid() && validOpaque(request.PermissionSetID, MaxIdentifierBytes) &&
		(request.MinCatalogRevision == nil || *request.MinCatalogRevision != 0) && validRequestGrants(request.Grants)
}

func validRequestGrants(grants []Grant) bool {
	if len(grants) > MaxGrants {
		return false
	}
	seen := make(map[string]struct{}, len(grants))
	for _, grant := range grants {
		if !validOpaque(grant.PermissionID, MaxIdentifierBytes) {
			return false
		}
		if _, duplicate := seen[grant.PermissionID]; duplicate {
			return false
		}
		seen[grant.PermissionID] = struct{}{}
	}
	return true
}

func validResponseGrants(grants []Grant) bool {
	if !validRequestGrants(grants) {
		return false
	}
	for index := 1; index < len(grants); index++ {
		if grants[index-1].PermissionID >= grants[index].PermissionID {
			return false
		}
	}
	return true
}

func validSystemCatalog(values []SystemPermission) bool {
	seen := make(map[string]struct{}, len(values))
	for index, value := range values {
		if !validOpaque(value.PermissionID, MaxIdentifierBytes) || !validText(value.Label, MaxLabelBytes, false) ||
			!validText(value.Category, MaxLabelBytes, false) || !validOptionalText(value.Description, MaxDescriptionBytes, true) ||
			(value.RequiresPermissionID != "" && !validOpaque(value.RequiresPermissionID, MaxIdentifierBytes)) ||
			(index > 0 && values[index-1].PermissionID >= value.PermissionID) {
			return false
		}
		if _, duplicate := seen[value.PermissionID]; duplicate {
			return false
		}
		seen[value.PermissionID] = struct{}{}
	}
	return true
}

func validRecordTypeCatalog(values []RecordType) bool {
	seen := make(map[string]struct{}, len(values))
	for index, value := range values {
		if !validOpaque(value.RecordTypeID, MaxIdentifierBytes) || !common.ValidObjectIdentifier(value.ResourceType) ||
			!validOpaque(value.StableName, MaxIdentifierBytes) || !validText(value.Label, MaxLabelBytes, false) ||
			(index > 0 && values[index-1].RecordTypeID >= value.RecordTypeID) {
			return false
		}
		if _, duplicate := seen[value.RecordTypeID]; duplicate {
			return false
		}
		seen[value.RecordTypeID] = struct{}{}
	}
	return true
}

func validCustomCatalog(values []CustomPermission) bool {
	seen := make(map[string]struct{}, len(values))
	for index, value := range values {
		if !validOpaque(value.PermissionID, MaxIdentifierBytes) || !validOpaque(value.StableName, MaxIdentifierBytes) ||
			!validText(value.Label, MaxLabelBytes, false) ||
			(index > 0 && values[index-1].PermissionID >= value.PermissionID) {
			return false
		}
		if _, duplicate := seen[value.PermissionID]; duplicate {
			return false
		}
		seen[value.PermissionID] = struct{}{}
	}
	return true
}

func onlySelectedReadFamily(selector ReadSelector, response ReadResponse) bool {
	lengths := map[ReadSelector]int{
		ReadSystemCatalog: len(response.SystemCatalog), ReadSystemGrants: len(response.SystemGrants),
		ReadRecordTypeCatalog: len(response.RecordTypeCatalog), ReadRecordTypeGrants: len(response.RecordTypeGrants),
		ReadCustomCatalog: len(response.CustomCatalog), ReadCustomGrants: len(response.CustomGrants),
	}
	for family, length := range lengths {
		if family != selector && length != 0 {
			return false
		}
	}
	return true
}

func validOpaque(value string, maximum int) bool {
	return validText(value, maximum, false) && strings.TrimSpace(value) == value
}

func validOptionalText(value string, maximum int, allowLineBreaks bool) bool {
	return value == "" || validText(value, maximum, allowLineBreaks)
}

func validText(value string, maximum int, allowLineBreaks bool) bool {
	if value == "" || len(value) > maximum || !utf8.ValidString(value) {
		return false
	}
	for _, r := range value {
		if r == 0 || (unicode.IsControl(r) && !(allowLineBreaks && (r == '\n' || r == '\r' || r == '\t'))) {
			return false
		}
	}
	return true
}

func validateSize(value any, maximum int, kind common.LimitKind) error {
	encoded, err := json.Marshal(value)
	if err != nil {
		return internal()
	}
	if len(encoded) > maximum {
		return &Failure{Category: common.FailureResourceExhausted, LimitKind: kind, Maximum: uint64(maximum)}
	}
	return nil
}

func invalid(component common.InvalidComponent) *Failure {
	return &Failure{Category: common.FailureInvalidRequest, InvalidComponent: component}
}

func internal() *Failure { return &Failure{Category: common.FailureInternal} }
