package effectivepermissionsv1

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

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

func ValidateRequest(ctx context.Context, request Request) error {
	if err := contextFailure(ctx); err != nil {
		return err
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(InvalidComponentDeadline)
	}
	if !validRequestShape(request) {
		return invalid(InvalidComponentOperation)
	}
	return validateSize(request, MaxSemanticRequestBytes, LimitSemanticRequest)
}

func validSelection(selection *Selection) bool {
	if selection == nil {
		return true
	}
	if len(selection.Sections) == 0 || len(selection.Sections) > MaxSelectedSections ||
		len(selection.ResourceTypes) > MaxSelectedResourceTypes {
		return false
	}
	previousSection := -1
	resourceScoped := false
	for _, section := range selection.Sections {
		order := sectionOrder(section)
		if order <= previousSection {
			return false
		}
		previousSection = order
		switch section {
		case SectionObjectPermissions, SectionFieldPermissions,
			SectionRecordTypePermissions:
			resourceScoped = true
		}
	}
	if resourceScoped != (len(selection.ResourceTypes) != 0) {
		return false
	}
	seen := make(map[string]struct{}, len(selection.ResourceTypes))
	previous := ""
	for index, resourceType := range selection.ResourceTypes {
		folded := strings.ToLower(resourceType)
		if !common.ValidObjectIdentifier(resourceType) ||
			(index > 0 && resourceType <= previous) {
			return false
		}
		if _, duplicate := seen[folded]; duplicate {
			return false
		}
		seen[folded] = struct{}{}
		previous = resourceType
	}
	return true
}

func sectionOrder(section Section) int {
	switch section {
	case SectionSystemPermissions:
		return 0
	case SectionCustomPermissions:
		return 1
	case SectionObjectPermissions:
		return 2
	case SectionFieldPermissions:
		return 3
	case SectionApplicationPermissions:
		return 4
	case SectionNavigationPermissions:
		return 5
	case SectionRecordTypePermissions:
		return 6
	default:
		return -1
	}
}

func ValidateResponse(response Response) error {
	if !common.ValidReceipts(response.Receipts, CapabilityName) {
		return incompatible()
	}
	if err := ValidateAuthorityFacts(response); err != nil {
		return err
	}
	return validateSize(response, MaxSemanticResponseBytes, LimitSemanticResponse)
}

// ValidateAuthorityFacts is the single shape validator for effective
// permission collections, including selected projections embedded in another
// Runtime response. The enclosing capability remains responsible for its own
// receipts and total response-envelope bound.
func ValidateAuthorityFacts(response Response) error {
	if len(response.SystemPermissions) > MaxSystemPermissions {
		return exhausted(LimitOperationCount, MaxSystemPermissions)
	}
	if len(response.CustomPermissions) > MaxCustomPermissions {
		return exhausted(LimitOperationCount, MaxCustomPermissions)
	}
	if len(response.ObjectPermissions) > MaxObjectPermissions {
		return exhausted(LimitOperationCount, MaxObjectPermissions)
	}
	if len(response.FieldPermissions) > MaxFieldPermissions {
		return exhausted(LimitFieldCount, MaxFieldPermissions)
	}
	if len(response.ApplicationPermissions) > MaxApplicationPermissions {
		return exhausted(LimitOperationCount, MaxApplicationPermissions)
	}
	if len(response.NavigationPermissions) > MaxNavigationPermissions {
		return exhausted(LimitOperationCount, MaxNavigationPermissions)
	}
	if len(response.RecordTypePermissions) > MaxRecordTypePermissions {
		return exhausted(LimitOperationCount, MaxRecordTypePermissions)
	}
	if !validPermissionGrants(response.SystemPermissions) ||
		!validPermissionGrants(response.CustomPermissions) ||
		!validObjectPermissions(response.ObjectPermissions) ||
		!validFieldPermissions(response.FieldPermissions) ||
		!validApplicationPermissions(response.ApplicationPermissions) ||
		!validNavigationPermissions(response.NavigationPermissions) ||
		!validRecordTypePermissions(response.RecordTypePermissions) {
		return internal()
	}
	return nil
}

// ValidateResponseForRequest proves that a selected response contains no
// unrequested section and no object-scoped fact outside the requested Catalog
// identities. ValidateResponse remains the shape validator for complete
// projections and for generic transport mapping.
func ValidateResponseForRequest(request Request, response Response) error {
	if err := ValidateResponse(response); err != nil {
		return err
	}
	if !validRequestShape(request) ||
		(request.MinCatalogRevision != nil &&
			response.Receipts.CatalogRevision < *request.MinCatalogRevision) {
		return internal()
	}
	selection := request.Selection
	if selection == nil {
		return nil
	}
	if (!selection.Requests(SectionSystemPermissions) && len(response.SystemPermissions) != 0) ||
		(!selection.Requests(SectionCustomPermissions) && len(response.CustomPermissions) != 0) ||
		(!selection.Requests(SectionObjectPermissions) && len(response.ObjectPermissions) != 0) ||
		(!selection.Requests(SectionFieldPermissions) && len(response.FieldPermissions) != 0) ||
		(!selection.Requests(SectionApplicationPermissions) && len(response.ApplicationPermissions) != 0) ||
		(!selection.Requests(SectionNavigationPermissions) && len(response.NavigationPermissions) != 0) ||
		(!selection.Requests(SectionRecordTypePermissions) && len(response.RecordTypePermissions) != 0) {
		return internal()
	}
	resources := make(map[string]struct{}, len(selection.ResourceTypes))
	for _, resourceType := range selection.ResourceTypes {
		resources[strings.ToLower(resourceType)] = struct{}{}
	}
	for _, permission := range response.ObjectPermissions {
		if _, selected := resources[strings.ToLower(permission.ResourceType)]; !selected {
			return internal()
		}
	}
	for _, permission := range response.FieldPermissions {
		if _, selected := resources[strings.ToLower(permission.ResourceType)]; !selected {
			return internal()
		}
	}
	for _, permission := range response.RecordTypePermissions {
		if _, selected := resources[strings.ToLower(permission.ResourceType)]; !selected {
			return internal()
		}
	}
	return nil
}

func validRequestShape(request Request) bool {
	return (request.TargetPrincipalID == "" || validOpaqueIdentifier(
		request.TargetPrincipalID,
		MaxPrincipalIdentifierBytes,
	)) && validSelection(request.Selection) &&
		(request.MinCatalogRevision == nil || *request.MinCatalogRevision > 0)
}

func validApplicationPermissions(permissions []ApplicationPermission) bool {
	if !validEffectiveVisibilityKeys(len(permissions), func(index int) string {
		return permissions[index].ApplicationID
	}) {
		return false
	}
	for _, permission := range permissions {
		if permission.IsDefault && !permission.CanView {
			return false
		}
	}
	return true
}

func validNavigationPermissions(permissions []NavigationPermission) bool {
	return validEffectiveVisibilityKeys(len(permissions), func(index int) string {
		return permissions[index].NavigationKey
	})
}

func validRecordTypePermissions(permissions []RecordTypePermission) bool {
	key := func(permission RecordTypePermission) string {
		return permission.ResourceType + "\x00" + permission.RecordTypeID
	}
	if !sort.SliceIsSorted(permissions, func(i, j int) bool {
		return key(permissions[i]) < key(permissions[j])
	}) {
		return false
	}
	type objectState struct{ defaults int }
	states := make(map[string]objectState, len(permissions))
	seenIDs := make(map[string]struct{}, len(permissions))
	for _, permission := range permissions {
		resourceKey := strings.ToLower(permission.ResourceType)
		idKey := resourceKey + "\x00" + permission.RecordTypeID
		if !common.ValidObjectIdentifier(permission.ResourceType) ||
			!validOpaqueIdentifier(permission.RecordTypeID, MaxPermissionIdentifierBytes) ||
			(permission.IsDefault && !permission.Available) {
			return false
		}
		if _, duplicate := seenIDs[idKey]; duplicate {
			return false
		}
		seenIDs[idKey] = struct{}{}
		state := states[resourceKey]
		if permission.IsDefault {
			state.defaults++
		}
		if state.defaults > 1 {
			return false
		}
		states[resourceKey] = state
	}
	return true
}

func validEffectiveVisibilityKeys(length int, key func(int) string) bool {
	seen := make(map[string]struct{}, length)
	for index := 0; index < length; index++ {
		value := key(index)
		folded := strings.ToLower(value)
		if !validOpaqueIdentifier(value, MaxPermissionIdentifierBytes) ||
			(index > 0 && value <= key(index-1)) {
			return false
		}
		if _, duplicate := seen[folded]; duplicate {
			return false
		}
		seen[folded] = struct{}{}
	}
	return true
}

func validPermissionGrants(grants []PermissionGrant) bool {
	if !sort.SliceIsSorted(grants, func(i, j int) bool {
		return grants[i].PermissionID < grants[j].PermissionID
	}) {
		return false
	}
	previous := ""
	for index, grant := range grants {
		if !validOpaqueIdentifier(grant.PermissionID, MaxPermissionIdentifierBytes) ||
			(index > 0 && grant.PermissionID == previous) {
			return false
		}
		previous = grant.PermissionID
	}
	return true
}

func validObjectPermissions(permissions []ObjectPermission) bool {
	if !sort.SliceIsSorted(permissions, func(i, j int) bool {
		return permissions[i].ResourceType < permissions[j].ResourceType
	}) {
		return false
	}
	seen := make(map[string]struct{}, len(permissions))
	for _, permission := range permissions {
		folded := strings.ToLower(permission.ResourceType)
		if !common.ValidObjectIdentifier(permission.ResourceType) {
			return false
		}
		if _, duplicate := seen[folded]; duplicate {
			return false
		}
		seen[folded] = struct{}{}
	}
	return true
}

func validFieldPermissions(permissions []FieldPermission) bool {
	key := func(permission FieldPermission) string {
		return permission.ResourceType + "\x00" + permission.FieldName
	}
	if !sort.SliceIsSorted(permissions, func(i, j int) bool {
		return key(permissions[i]) < key(permissions[j])
	}) {
		return false
	}
	seen := make(map[string]struct{}, len(permissions))
	for _, permission := range permissions {
		folded := strings.ToLower(permission.ResourceType) + "\x00" +
			strings.ToLower(permission.FieldName)
		if !common.ValidObjectIdentifier(permission.ResourceType) ||
			!common.ValidFieldIdentifier(permission.FieldName) {
			return false
		}
		if _, duplicate := seen[folded]; duplicate {
			return false
		}
		seen[folded] = struct{}{}
	}
	return true
}

func validOpaqueIdentifier(value string, maximum int) bool {
	if value == "" || len(value) > maximum || !utf8.ValidString(value) ||
		strings.TrimSpace(value) != value {
		return false
	}
	for _, char := range value {
		if unicode.IsControl(char) {
			return false
		}
	}
	return true
}

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

func contextFailure(ctx context.Context) error {
	if ctx == nil {
		return invalid(InvalidComponentDeadline)
	}
	if err := ctx.Err(); err != nil {
		if err == context.DeadlineExceeded {
			return &Failure{Category: FailureDeadlineExceeded}
		}
		return &Failure{Category: FailureCanceled}
	}
	return nil
}

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

func exhausted(kind LimitKind, maximum uint64) *Failure {
	return &Failure{
		Category:  FailureResourceExhausted,
		LimitKind: kind,
		Maximum:   maximum,
	}
}

func incompatible() *Failure {
	return &Failure{
		Category: FailureIncompatible, RequiredCapability: CapabilityName,
		SupportedMajor: ProtocolMajor, SupportedMinorMin: ProtocolMinor,
		SupportedMinorMax: ProtocolMinor,
	}
}

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