package mailboxauthorizationv1

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

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

func ValidateRequest(ctx context.Context, request Request) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !request.Operation.Valid() ||
		(request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0) {
		return invalid()
	}
	raw, err := json.Marshal(request)
	if err != nil || len(raw) > MaxSemanticRequestBytes {
		return &Failure{Category: common.FailureResourceExhausted, LimitKind: common.LimitSemanticRequest, Maximum: MaxSemanticRequestBytes}
	}
	return nil
}

func ValidateResponse(request Request, response Response) error {
	if (request.Operation == OperationBegin && !bounded(response.ReauthorizationHandle, MaxAuthorizationHandleBytes, true)) ||
		(request.Operation != OperationBegin && response.ReauthorizationHandle != "") {
		return internal()
	}
	return validateResponseReceipts(request.MinCatalogRevision, response)
}

func ValidateReauthorizeRequest(ctx context.Context, request ReauthorizeRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !bounded(request.ReauthorizationHandle, MaxAuthorizationHandleBytes, true) ||
		(request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0) {
		return invalid()
	}
	raw, err := json.Marshal(request)
	if err != nil || len(raw) > MaxSemanticRequestBytes {
		return &Failure{Category: common.FailureResourceExhausted, LimitKind: common.LimitSemanticRequest, Maximum: MaxSemanticRequestBytes}
	}
	return nil
}

func ValidateReauthorizeResponse(request ReauthorizeRequest, response Response) error {
	if response.ReauthorizationHandle != "" {
		return internal()
	}
	return validateResponseReceipts(request.MinCatalogRevision, response)
}

func validateResponseReceipts(minimum *uint64, response Response) error {
	if !identifier(response.AuthorizedSubjectID) ||
		response.Receipts.ProtocolVersion != "1.0" ||
		response.Receipts.CapabilityVersion != CapabilityName ||
		response.Receipts.CatalogRevision == 0 || response.Receipts.SchemaGeneration == 0 ||
		response.Receipts.EngineGeneration == 0 || response.Receipts.SecurityEpoch == 0 ||
		(minimum != nil && response.Receipts.CatalogRevision < *minimum) {
		return internal()
	}
	raw, err := json.Marshal(response)
	if err != nil || len(raw) > MaxSemanticResponseBytes {
		return internal()
	}
	return nil
}

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 identifier(value string) bool {
	return bounded(value, MaxIdentifierBytes, true)
}

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

func invalid() error {
	return &Failure{Category: common.FailureInvalidRequest, InvalidComponent: common.InvalidComponentCommandData}
}
func invalidDeadline() error {
	return &Failure{Category: common.FailureInvalidRequest, InvalidComponent: common.InvalidComponentDeadline}
}
func internal() error { return &Failure{Category: common.FailureInternal} }
