package approvalnotificationflowv1

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

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

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

func ValidateRequest(ctx context.Context, request Request) 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}
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(InvalidComponentDeadline)
	}
	if !validRequestShape(request) {
		return invalid(InvalidComponentOperation)
	}
	return nil
}

func ValidateResponse(response Response) error {
	if !common.ValidReceipts(response.Receipts, CapabilityName) {
		return incompatible()
	}
	if len(response.Flows) > MaxFlows {
		return exhausted(LimitRecordCount, MaxFlows)
	}
	if !withinRawBudget(response) {
		return exhausted(LimitSemanticResponse, MaxSemanticResponseBytes)
	}
	previous := ""
	for index, flow := range response.Flows {
		key := strings.ToLower(flow.Name)
		if !validFlowDescriptor(flow) || (index > 0 && key <= previous) {
			return internal()
		}
		previous = key
	}
	encoded, err := json.Marshal(response)
	if err != nil {
		return internal()
	}
	if len(encoded) > MaxSemanticResponseBytes {
		return exhausted(LimitSemanticResponse, MaxSemanticResponseBytes)
	}
	return nil
}

// ValidateResponseForRequest binds a descriptor result to the minimum Catalog
// revision requested by the caller.
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()
	}
	return nil
}

func validRequestShape(request Request) bool {
	return request.MinCatalogRevision == nil || *request.MinCatalogRevision > 0
}

func validFlowDescriptor(flow FlowDescriptor) bool {
	return len(flow.Name) > 0 && len(flow.Name) <= MaxNameBytes &&
		flowNamePattern.MatchString(flow.Name) &&
		validRequiredText(flow.Label, MaxLabelBytes) &&
		validText(flow.Description, MaxDescriptionBytes)
}

func withinRawBudget(response Response) bool {
	remaining := MaxSemanticResponseBytes
	for _, value := range []string{response.Receipts.ProtocolVersion, response.Receipts.CapabilityVersion} {
		if len(value) > remaining {
			return false
		}
		remaining -= len(value)
	}
	for _, flow := range response.Flows {
		for _, value := range []string{flow.Name, flow.Label, flow.Description} {
			if len(value) > remaining {
				return false
			}
			remaining -= len(value)
		}
	}
	return true
}

func validRequiredText(value string, maximum int) bool {
	return strings.TrimSpace(value) != "" && strings.TrimSpace(value) == value &&
		len(value) <= maximum && utf8.ValidString(value)
}

func validText(value string, maximum int) bool {
	return len(value) <= maximum && utf8.ValidString(value)
}

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

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

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

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