package applicationresultv1

import (
	"context"
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"

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

var digestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)

func ValidateExecutionRef(ref ExecutionRef) error {
	if !validExecutionRef(ref) {
		return invalid()
	}
	return nil
}

func ValidateGetResultRequest(ctx context.Context, request GetResultRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !validGetResultRequest(request) {
		return invalid()
	}
	return nil
}

func ValidateApplicationResult(result ApplicationResult) error {
	if !validApplicationResultContent(result) {
		return internal()
	}
	receipt, err := ComputeRuntimeCommitReceipt(result)
	if err != nil || receipt != result.RuntimeCommitReceipt {
		return internal()
	}
	return nil
}

func ValidateGetResultResponse(request GetResultRequest, response GetResultResponse) error {
	if !validGetResultRequest(request) || response.CommandID != request.CommandID ||
		response.ExecutionRefDigest != request.ExecutionRefDigest || !response.State.Valid() {
		return internal()
	}
	if response.State == ResultPending {
		if response.Result != nil {
			return internal()
		}
		return nil
	}
	if response.Result == nil || response.Result.CommandID != request.CommandID ||
		response.Result.ExecutionRefDigest != request.ExecutionRefDigest ||
		ValidateApplicationResult(*response.Result) != nil {
		return internal()
	}
	return nil
}

// SameApplicationResult reports exact equality of two valid Runtime-owned
// terminal facts. A duplicate command/ref binding must always satisfy it.
func SameApplicationResult(left, right ApplicationResult) bool {
	return left == right && ValidateApplicationResult(left) == nil
}

func validApplicationResultContent(result ApplicationResult) bool {
	return validOpaque(result.CommandID, MaxIdentifierBytes) &&
		digestPattern.MatchString(result.ExecutionRefDigest) && result.Outcome.Valid() &&
		digestPattern.MatchString(result.ResultDigest) && result.FailureCode.ValidFor(result.Outcome)
}

func validGetResultRequest(request GetResultRequest) bool {
	return validOpaque(request.CommandID, MaxIdentifierBytes) &&
		digestPattern.MatchString(request.ExecutionRefDigest)
}

func validExecutionRef(ref ExecutionRef) bool {
	return validOpaque(ref.ExecutionID, MaxIdentifierBytes) &&
		validOpaque(ref.PackGenerationID, MaxIdentifierBytes) &&
		digestPattern.MatchString(ref.ServingEpochDigest) &&
		validOpaque(ref.DefinitionRef, MaxIdentifierBytes) &&
		validOpaque(ref.DefinitionVersion, MaxIdentifierBytes) &&
		digestPattern.MatchString(ref.InputDigest)
}

func validOpaque(value string, maximum int) bool {
	if value == "" || len(value) > maximum || !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 &Failure{Category: common.FailureInvalidRequest, InvalidComponent: 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 &Failure{Category: common.FailureInvalidRequest, InvalidComponent: common.InvalidComponentDeadline}
	}
	return nil
}

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

func exhausted(maximum int) *Failure {
	return &Failure{
		Category:  common.FailureResourceExhausted,
		LimitKind: common.LimitSemanticResponse,
		Maximum:   uint64(maximum),
	}
}

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