package flowv1

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

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

func ValidateRequest(ctx context.Context, request Request) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !validOpaque(request.DefinitionRef) || !validOpaque(request.DefinitionVersion) ||
		!validOpaque(request.IdempotencyKey) || !validCanonicalJSON(request.CanonicalInputs, MaxSemanticRequestBytes) {
		return invalid()
	}
	return validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest)
}

func ValidateCatalogActionRequest(ctx context.Context, request CatalogActionRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !validOpaque(request.IdempotencyKey) ||
		!validCanonicalJSON(request.CanonicalInputs, MaxSemanticRequestBytes) ||
		!validOpaque(request.CatalogAction.ComponentRef) ||
		!validOpaque(request.CatalogAction.ActionRef) {
		return invalid()
	}
	return validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest)
}

func ValidateCatalogActionPreviewRequest(
	ctx context.Context,
	request CatalogActionPreviewRequest,
) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !request.Mode.Valid() || !validOpaque(request.CatalogAction.ComponentRef) ||
		!validOpaque(request.CatalogAction.ActionRef) {
		return invalid()
	}
	switch request.Mode {
	case CatalogActionPreviewCreateRecords:
		if request.RecordID != "" ||
			!validCanonicalJSON(request.CanonicalInputs, MaxSemanticRequestBytes) {
			return invalid()
		}
	case CatalogActionPreviewOutputs:
		if len(request.CanonicalInputs) != 0 || !validOpaque(request.RecordID) {
			return invalid()
		}
	default:
		return invalid()
	}
	return validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest)
}

func ValidateCatalogActionPreviewResponse(
	request CatalogActionPreviewRequest,
	response CatalogActionPreviewResponse,
) error {
	if !common.ValidReceipts(response.Receipts, CapabilityName) ||
		response.Receipts.CatalogRevision == 0 ||
		response.Receipts.SchemaGeneration == 0 ||
		response.Receipts.EngineGeneration == 0 ||
		response.Receipts.SecurityEpoch == 0 ||
		(request.MinCatalogRevision != nil &&
			response.Receipts.CatalogRevision < *request.MinCatalogRevision) ||
		!validCanonicalJSON(response.CanonicalResult, MaxSemanticResponseBytes) {
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

func ValidateResponse(response Response) error {
	if !common.ValidReceipts(response.Receipts, CapabilityName) ||
		!validOpaque(response.ExecutionID) ||
		!validCanonicalJSON(response.CanonicalOutputs, MaxSemanticResponseBytes) {
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

func ValidateResponseV2(response ResponseV2) error {
	if !common.ValidReceipts(response.Receipts, CapabilityName) ||
		!validOpaque(response.ExecutionID) ||
		!validFlowResultV2(response) {
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

func validFlowResultV2(response ResponseV2) bool {
	if len(response.Errors) == 0 {
		return validCanonicalJSON(response.CanonicalOutputs, MaxSemanticResponseBytes)
	}
	if len(response.CanonicalOutputs) != 0 || len(response.Errors) > MaxExecutionErrors {
		return false
	}
	for _, executionError := range response.Errors {
		if !validExecutionError(executionError) {
			return false
		}
	}
	return true
}

func validExecutionError(executionError ExecutionError) bool {
	if !validErrorCode(executionError.Code) ||
		!validSafeText(executionError.Message, MaxErrorMessageBytes) ||
		len(executionError.Fields) > MaxFieldsPerExecutionError {
		return false
	}
	for _, field := range executionError.Fields {
		if !common.ValidFieldIdentifier(field) {
			return false
		}
	}
	return true
}

func validErrorCode(value string) bool {
	if value == "" || len(value) > MaxIdentifierBytes {
		return false
	}
	for index, r := range value {
		if index == 0 && (r < 'A' || r > 'Z') {
			return false
		}
		if index > 0 && (r < 'A' || r > 'Z') && (r < '0' || r > '9') && r != '_' {
			return false
		}
	}
	return true
}

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

func validCanonicalJSON(value []byte, maximum int) bool {
	if len(value) == 0 || len(value) > maximum || !utf8.Valid(value) {
		return false
	}
	canonical, err := mutation.CanonicalSourceJSON(string(value))
	return err == nil && string(value) == canonical
}

func validOpaque(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 validateContext(ctx context.Context) error {
	if ctx == nil {
		return invalidDeadline()
	}
	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 invalidDeadline()
	}
	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() *Failure {
	return &Failure{Category: common.FailureInvalidRequest, InvalidComponent: common.InvalidComponentCommandData}
}
func invalidDeadline() *Failure {
	return &Failure{Category: common.FailureInvalidRequest, InvalidComponent: common.InvalidComponentDeadline}
}
func internal() *Failure { return &Failure{Category: common.FailureInternal} }
