package listviewv1

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

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

var stableNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)

func ValidateReadRequest(ctx context.Context, request ReadRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !request.Selector.Valid() {
		return invalid(common.InvalidComponentOperation)
	}
	if !common.ValidObjectIdentifier(request.ObjectID) {
		return invalid(common.InvalidComponentObjectID)
	}
	if request.Selector == ReadList && request.ViewID != "" {
		return invalid(common.InvalidComponentSavedViewID)
	}
	if request.Selector == ReadDescribe && !validViewID(request.ViewID) {
		return invalid(common.InvalidComponentSavedViewID)
	}
	if request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0 {
		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) {
		return internal()
	}
	switch request.Selector {
	case ReadList:
		if response.View != nil || len(response.Views) > MaxViews {
			return internal()
		}
		seen := make(map[string]struct{}, len(response.Views))
		for index, view := range response.Views {
			if !validView(view) || view.ObjectID != request.ObjectID {
				return internal()
			}
			if _, exists := seen[view.ViewID]; exists {
				return internal()
			}
			seen[view.ViewID] = struct{}{}
			if index > 0 && response.Views[index-1].ViewID >= view.ViewID {
				return internal()
			}
		}
	case ReadDescribe:
		if len(response.Views) != 0 || response.View == nil || !validView(*response.View) ||
			response.View.ViewID != request.ViewID || response.View.ObjectID != request.ObjectID {
			return internal()
		}
	default:
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

func ValidateMutateRequest(ctx context.Context, request MutateRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !request.Operation.Valid() {
		return invalid(common.InvalidComponentOperation)
	}
	if !common.ValidObjectIdentifier(request.ObjectID) {
		return invalid(common.InvalidComponentCommandData)
	}
	if request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0 {
		return invalid(common.InvalidComponentCommandData)
	}
	switch request.Operation {
	case MutationCreate:
		if request.ViewID != "" || request.Patch == nil || request.Patch.Label == nil ||
			!validLabel(*request.Patch.Label) || !validPatch(*request.Patch) {
			return invalid(common.InvalidComponentCommandData)
		}
	case MutationUpdate:
		if !validViewID(request.ViewID) || request.Patch == nil || !validPatch(*request.Patch) {
			return invalid(common.InvalidComponentCommandData)
		}
	case MutationDelete:
		if !validViewID(request.ViewID) || request.Patch != nil {
			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()
	}
	switch request.Operation {
	case MutationCreate:
		if response.View == nil || !validView(*response.View) || response.View.ObjectID != request.ObjectID {
			return internal()
		}
	case MutationUpdate:
		if response.View == nil || !validView(*response.View) || response.View.ObjectID != request.ObjectID ||
			response.View.ViewID != request.ViewID {
			return internal()
		}
	case MutationDelete:
		if response.View != nil {
			return internal()
		}
	default:
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

// SortViews orders a LIST result by opaque view identity. Runtime calls this
// before validation so concurrent repository completion cannot affect wire
// order.
func SortViews(views []View) {
	sort.SliceStable(views, func(i, j int) bool { return views[i].ViewID < views[j].ViewID })
}

func validReadRequestShape(request ReadRequest) bool {
	if !request.Selector.Valid() || !common.ValidObjectIdentifier(request.ObjectID) ||
		(request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0) {
		return false
	}
	return (request.Selector == ReadList && request.ViewID == "") ||
		(request.Selector == ReadDescribe && validViewID(request.ViewID))
}

func validMutateRequestShape(request MutateRequest) bool {
	if !request.Operation.Valid() || !common.ValidObjectIdentifier(request.ObjectID) ||
		(request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0) {
		return false
	}
	switch request.Operation {
	case MutationCreate:
		return request.ViewID == "" && request.Patch != nil && request.Patch.Label != nil &&
			validLabel(*request.Patch.Label) && validPatch(*request.Patch)
	case MutationUpdate:
		return validViewID(request.ViewID) && request.Patch != nil && validPatch(*request.Patch)
	case MutationDelete:
		return validViewID(request.ViewID) && request.Patch == nil
	default:
		return false
	}
}

func validView(view View) bool {
	if !validViewID(view.ViewID) || !common.ValidObjectIdentifier(view.ObjectID) ||
		!validLabel(view.Label) || !validFilter(view.FilterExpression, MaxFilterExpressionBytes) ||
		!validFilter(view.FilterLogic, MaxFilterLogicBytes) || !validFieldIDs(view.FieldIDs) ||
		!validStableName(view.StableName) {
		return false
	}
	if view.SortFieldID == "" {
		return view.SortDirection == ""
	}
	return common.ValidFieldIdentifier(view.SortFieldID) && view.SortDirection.Valid()
}

func validPatch(patch Patch) bool {
	if patch.Label != nil && !validLabel(*patch.Label) {
		return false
	}
	if patch.FilterExpression != nil && !validFilter(*patch.FilterExpression, MaxFilterExpressionBytes) {
		return false
	}
	if patch.FilterLogic != nil && !validFilter(*patch.FilterLogic, MaxFilterLogicBytes) {
		return false
	}
	if patch.Fields != nil && !validFieldIDs(patch.Fields.FieldIDs) {
		return false
	}
	if patch.SortFieldID != nil && *patch.SortFieldID != "" && !common.ValidFieldIdentifier(*patch.SortFieldID) {
		return false
	}
	if patch.SortDirection != nil && *patch.SortDirection != "" && !patch.SortDirection.Valid() {
		return false
	}
	// Sorting is one state invariant. Requiring both PATCH members lets a
	// server validate the mutation before committing instead of discovering an
	// invalid half-updated view only while validating the response.
	if (patch.SortFieldID == nil) != (patch.SortDirection == nil) {
		return false
	}
	if patch.SortFieldID != nil && ((*patch.SortFieldID == "") != (*patch.SortDirection == "")) {
		return false
	}
	if patch.StableName != nil && !validStableName(*patch.StableName) {
		return false
	}
	return true
}

func validFieldIDs(values []string) bool {
	if len(values) > MaxFields {
		return false
	}
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		if !common.ValidFieldIdentifier(value) {
			return false
		}
		if _, exists := seen[value]; exists {
			return false
		}
		seen[value] = struct{}{}
	}
	return true
}

func validViewID(value string) bool { return validOpaque(value, MaxViewIdentifierBytes) }

func validStableName(value string) bool {
	return len(value) > 0 && len(value) <= MaxStableNameBytes && stableNamePattern.MatchString(value)
}

func validLabel(value string) bool {
	return validText(value, MaxLabelBytes, false) && strings.TrimSpace(value) == value
}

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

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

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 validateContext(ctx context.Context) error {
	if ctx == nil {
		return invalid(common.InvalidComponentDeadline)
	}
	if err := ctx.Err(); err != nil {
		if err == context.DeadlineExceeded {
			return &Failure{Category: common.FailureDeadlineExceeded}
		}
		return &Failure{Category: common.FailureCanceled}
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(common.InvalidComponentDeadline)
	}
	return nil
}

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} }
