package recordmutationv1

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

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

var (
	apiIdentifierPattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
	commandIDPattern     = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:@-]*$`)
	decimalPattern       = regexp.MustCompile(`^-?(0|[1-9][0-9]*)$`)
	jsonNumberPattern    = regexp.MustCompile(`^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?$`)
)

func ValidateRequest(ctx context.Context, request Request) error {
	if err := contextFailure(ctx); err != nil {
		return err
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(InvalidComponentDeadline)
	}
	return validateRequestBody(request)
}

func validateRequestBody(request Request) error {
	if err := validateCommandID(request.CommandID); err != nil {
		return err
	}
	if !request.BatchMode.Valid() {
		return invalid(InvalidComponentBatchMode)
	}
	if !request.UseCase.Valid() {
		return invalid(InvalidComponentCommandData)
	}
	if len(request.Operations) == 0 {
		return invalid(InvalidComponentOperation)
	}
	if len(request.Operations) > MaxOperations {
		return exhausted(LimitOperationCount, MaxOperations)
	}
	if request.UseCase == MutationUseCaseSingle && len(request.Operations) != 1 {
		return invalid(InvalidComponentOperation)
	}
	for _, operation := range request.Operations {
		if err := validateOperation(operation); err != nil {
			return err
		}
	}
	if SemanticRequestSize(request) > MaxSemanticRequestBytes {
		return exhausted(LimitSemanticRequest, MaxSemanticRequestBytes)
	}
	return nil
}

func ValidateStatusRequest(ctx context.Context, request StatusRequest) error {
	if err := contextFailure(ctx); err != nil {
		return err
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(InvalidComponentDeadline)
	}
	if err := validateCommandID(request.CommandID); err != nil {
		return err
	}
	if !validCommandDigest(request.CommandDigest) {
		return invalid(InvalidComponentCommandData)
	}
	return nil
}

// StatusRequestFor derives the reconciliation binding from the original
// accepted command. CommandID remains the sole command identity; the digest
// proves which immutable content that identity names.
func StatusRequestFor(request Request) (StatusRequest, error) {
	shape, err := CommandShapeFor(request)
	if err != nil {
		return StatusRequest{}, err
	}
	return StatusRequest{CommandID: shape.CommandID, CommandDigest: shape.CommandDigest}, nil
}

// CommandShapeFor derives the minimum immutable outcome shape from a complete
// semantic request. Runtime may reconstruct the same shape from its durable
// command row and operation slots without retaining caller field values.
func CommandShapeFor(request Request) (CommandShape, error) {
	if err := validateRequestBody(request); err != nil {
		return CommandShape{}, err
	}
	digest, err := CommandDigest(request)
	if err != nil {
		return CommandShape{}, invalid(InvalidComponentCommandData)
	}
	kinds := make([]OperationKind, len(request.Operations))
	objectIDs := make([]string, len(request.Operations))
	recordIDs := make([]string, len(request.Operations))
	for i := range request.Operations {
		kinds[i] = request.Operations[i].Kind
		objectIDs[i] = request.Operations[i].ObjectID
		recordIDs[i] = request.Operations[i].RecordID
	}
	return CommandShape{
		CommandID: request.CommandID, CommandDigest: digest,
		BatchMode: request.BatchMode, OperationKinds: kinds,
		OperationObjectIDs: objectIDs, OperationRecordIDs: recordIDs,
	}, nil
}

func validateCommandID(value string) error {
	if len(value) > MaxCommandIdentifierBytes {
		return exhausted(LimitCommandID, MaxCommandIdentifierBytes)
	}
	if !utf8.ValidString(value) || !commandIDPattern.MatchString(value) {
		return invalid(InvalidComponentCommandID)
	}
	return nil
}

func validateOperation(operation Operation) error {
	if !operation.Kind.Valid() {
		return invalid(InvalidComponentOperation)
	}
	if operation.ObjectID != "" && !validAPIIdentifier(operation.ObjectID, MaxObjectIdentifierBytes) {
		if len(operation.ObjectID) > MaxObjectIdentifierBytes {
			return exhausted(LimitObjectID, MaxObjectIdentifierBytes)
		}
		return invalid(InvalidComponentObjectID)
	}
	if operation.RecordID != "" {
		if !utf8.ValidString(operation.RecordID) {
			return invalid(InvalidComponentRecordID)
		}
		if len(operation.RecordID) > MaxRecordIdentifierBytes {
			return exhausted(LimitRecordID, MaxRecordIdentifierBytes)
		}
	}
	switch operation.Kind {
	case OperationCreate:
		if operation.ObjectID == "" || operation.RecordID != "" || operation.ExternalIdentity != nil {
			return invalid(InvalidComponentOperation)
		}
	case OperationUpdate:
		if operation.ObjectID == "" || operation.RecordID == "" || operation.ExternalIdentity != nil {
			return invalid(InvalidComponentOperation)
		}
	case OperationDelete:
		if operation.RecordID == "" || operation.ExternalIdentity != nil || len(operation.Fields) != 0 {
			return invalid(InvalidComponentOperation)
		}
	case OperationUpsert:
		if operation.ObjectID == "" || operation.RecordID != "" || operation.ExternalIdentity == nil {
			return invalid(InvalidComponentOperation)
		}
		if !validAPIIdentifier(operation.ExternalIdentity.FieldID, MaxFieldIdentifierBytes) ||
			!utf8.ValidString(operation.ExternalIdentity.Value) || strings.TrimSpace(operation.ExternalIdentity.Value) == "" {
			return invalid(InvalidComponentOperation)
		}
	}
	seenFields := make(map[string]struct{}, len(operation.Fields))
	if len(operation.Fields) > MaxFieldsPerOperation {
		return exhausted(LimitFieldCount, MaxFieldsPerOperation)
	}
	for _, field := range operation.Fields {
		if err := validateField(field); err != nil {
			return err
		}
		if operation.ExternalIdentity != nil && strings.EqualFold(field.Name, operation.ExternalIdentity.FieldID) {
			return invalid(InvalidComponentFieldValue)
		}
		if _, duplicate := seenFields[field.Name]; duplicate {
			return invalid(InvalidComponentFieldValue)
		}
		seenFields[field.Name] = struct{}{}
	}
	return nil
}

func validateField(field FieldValue) error {
	if !validAPIIdentifier(field.Name, MaxFieldIdentifierBytes) {
		if len(field.Name) > MaxFieldIdentifierBytes {
			return exhausted(LimitFieldID, MaxFieldIdentifierBytes)
		}
		return invalid(InvalidComponentFieldValue)
	}
	if !field.Kind.Valid() {
		return invalid(InvalidComponentFieldValue)
	}
	arms := 0
	for _, present := range []bool{
		field.String != nil, field.Boolean != nil, field.Integer != nil,
		field.Decimal != nil, field.Date != nil, field.DateTime != nil,
		field.Reference != nil, field.Number != nil, field.JSON != nil,
	} {
		if present {
			arms++
		}
	}
	if field.Null {
		if arms != 0 || field.Kind != ValueKindNull {
			return invalid(InvalidComponentFieldValue)
		}
		return nil
	}
	if arms != 1 || field.Kind == ValueKindNull {
		return invalid(InvalidComponentFieldValue)
	}
	switch field.Kind {
	case ValueKindString:
		if field.String == nil || !utf8.ValidString(*field.String) {
			return invalid(InvalidComponentFieldValue)
		}
	case ValueKindBoolean:
		if field.Boolean == nil {
			return invalid(InvalidComponentFieldValue)
		}
	case ValueKindInteger:
		if field.Integer == nil {
			return invalid(InvalidComponentFieldValue)
		}
	case ValueKindDecimal:
		if field.Decimal == nil || !decimalPattern.MatchString(field.Decimal.Coefficient) ||
			len(strings.TrimPrefix(field.Decimal.Coefficient, "-")) > MaxDecimalPrecision ||
			field.Decimal.Scale > MaxDecimalScale {
			return invalid(InvalidComponentFieldValue)
		}
	case ValueKindDate:
		if field.Date == nil {
			return invalid(InvalidComponentFieldValue)
		}
		if _, err := time.Parse("2006-01-02", *field.Date); err != nil {
			return invalid(InvalidComponentFieldValue)
		}
	case ValueKindDateTime:
		if field.DateTime == nil {
			return invalid(InvalidComponentFieldValue)
		}
		parsed, err := time.Parse(time.RFC3339Nano, *field.DateTime)
		if err != nil || parsed.Location() != time.UTC || !strings.HasSuffix(*field.DateTime, "Z") {
			return invalid(InvalidComponentFieldValue)
		}
	case ValueKindReference:
		if field.Reference == nil || !utf8.ValidString(*field.Reference) || *field.Reference == "" {
			return invalid(InvalidComponentFieldValue)
		}
		if len(*field.Reference) > MaxRecordIdentifierBytes {
			return exhausted(LimitRecordID, MaxRecordIdentifierBytes)
		}
	case ValueKindNumber:
		if field.Number == nil || !utf8.ValidString(*field.Number) || !jsonNumberPattern.MatchString(*field.Number) {
			return invalid(InvalidComponentFieldValue)
		}
	case ValueKindJSON:
		if field.JSON == nil || !utf8.ValidString(*field.JSON) {
			return invalid(InvalidComponentFieldValue)
		}
		if _, err := CanonicalSourceJSON(*field.JSON); err != nil {
			return invalid(InvalidComponentFieldValue)
		}
	}
	return nil
}

func ValidateResponse(request Request, response Response) error {
	shape, err := CommandShapeFor(request)
	if err != nil {
		return internal()
	}
	if response.CommandID != shape.CommandID || response.CommandDigest != shape.CommandDigest ||
		!common.ValidReceipts(response.Receipts, CapabilityName) {
		return internal()
	}
	if !validUTCInstant(response.OutcomeExpiresAtUTC) {
		return internal()
	}
	if err := validateResults(shape, response.Results); err != nil {
		return err
	}
	if responseSize(response) > MaxSemanticResponseBytes {
		return exhausted(LimitSemanticResponse, MaxSemanticResponseBytes)
	}
	return nil
}

func ValidateStatusResponse(shape CommandShape, request StatusRequest, response StatusResponse) error {
	if err := validateCommandShape(shape); err != nil {
		return internal()
	}
	if request.CommandID != shape.CommandID || request.CommandDigest != shape.CommandDigest ||
		response.CommandID != shape.CommandID || response.CommandDigest != shape.CommandDigest || !response.State.Valid() {
		return internal()
	}
	if response.State != CommandCompleted {
		if len(response.Results) != 0 || response.Receipts != nil || response.OutcomeExpiresAtUTC != "" {
			return internal()
		}
		return nil
	}
	if response.Receipts == nil || !common.ValidReceipts(*response.Receipts, CapabilityName) ||
		!validUTCInstant(response.OutcomeExpiresAtUTC) {
		return internal()
	}
	if err := validateResults(shape, response.Results); err != nil {
		return err
	}
	if responseSize(response) > MaxSemanticResponseBytes {
		return exhausted(LimitSemanticResponse, MaxSemanticResponseBytes)
	}
	return nil
}

func validateCommandShape(shape CommandShape) error {
	if err := validateCommandID(shape.CommandID); err != nil || !validCommandDigest(shape.CommandDigest) ||
		!shape.BatchMode.Valid() || len(shape.OperationKinds) == 0 || len(shape.OperationKinds) > MaxOperations ||
		len(shape.OperationObjectIDs) != len(shape.OperationKinds) ||
		len(shape.OperationRecordIDs) != len(shape.OperationKinds) {
		return internal()
	}
	for i, kind := range shape.OperationKinds {
		if !kind.Valid() {
			return internal()
		}
		objectID := shape.OperationObjectIDs[i]
		if (kind != OperationDelete && objectID == "") ||
			(objectID != "" && !validAPIIdentifier(objectID, MaxObjectIdentifierBytes)) {
			return internal()
		}
		recordID := shape.OperationRecordIDs[i]
		if !utf8.ValidString(recordID) || len(recordID) > MaxRecordIdentifierBytes ||
			((kind == OperationCreate || kind == OperationUpsert) && recordID != "") ||
			(kind != OperationCreate && kind != OperationUpsert && recordID == "") {
			return internal()
		}
	}
	return nil
}

func validateResults(shape CommandShape, results []OperationResult) error {
	if len(results) != len(shape.OperationKinds) {
		return internal()
	}
	successes := 0
	causalFailures := 0
	policyDenials := 0
	objectNotFound := 0
	processingHalted := 0
	for i, result := range results {
		if err := validateResult(uint32(i), shape.OperationKinds[i], shape.OperationObjectIDs[i], shape.OperationRecordIDs[i], result); err != nil {
			return err
		}
		if result.Success {
			successes++
			continue
		}
		hasRollbackMarker := false
		for _, operationError := range result.Errors {
			if operationError.Kind == ErrorAtomicRolledBack {
				hasRollbackMarker = true
			}
			if operationError.Kind == ErrorPolicyDenied {
				policyDenials++
			}
			if operationError.Kind == ErrorObjectNotFound {
				objectNotFound++
			}
			if operationError.Kind == ErrorProcessingHalted {
				processingHalted++
			}
		}
		if shape.BatchMode == BatchIndependent {
			if hasRollbackMarker {
				return internal()
			}
			continue
		}
		if hasRollbackMarker {
			// A rollback marker describes a sibling that did not own the
			// domain failure. Combining it with another error would make the
			// causal operation ambiguous.
			if len(result.Errors) != 1 {
				return internal()
			}
			continue
		}
		causalFailures++
	}
	// Missing Catalog objects are exclusively a command preflight: one position
	// owns the cause, every sibling is halted, and no operation may have
	// committed. A policy denial has the same zero-effect shape when every
	// sibling is halted. Policy is also re-evaluated inside the owning mutation
	// transaction, however, so a later denial is a valid per-operation outcome:
	// independent commands retain already committed sibling results, while an
	// atomic command reports the causal denial plus rollback markers.
	commandPreflight := objectNotFound != 0 || processingHalted != 0
	if commandPreflight {
		preflightFailures := policyDenials + objectNotFound
		if successes != 0 || preflightFailures != 1 || processingHalted != len(results)-1 {
			return internal()
		}
		for _, result := range results {
			if len(result.Errors) != 1 || (result.Errors[0].Kind != ErrorPolicyDenied &&
				result.Errors[0].Kind != ErrorObjectNotFound && result.Errors[0].Kind != ErrorProcessingHalted) {
				return internal()
			}
		}
		return nil
	}
	if shape.BatchMode != BatchAtomic {
		return nil
	}
	// Atomic terminal outcomes have only two legal shapes: every operation
	// committed, or no operation committed and exactly one position retains
	// the causal domain failure while every sibling is marked rolled back.
	if successes == len(results) {
		return nil
	}
	if successes != 0 || causalFailures != 1 {
		return internal()
	}
	return nil
}

func validateResult(index uint32, kind OperationKind, expectedObjectID, expectedRecordID string, result OperationResult) error {
	if result.Index != index || result.Kind != kind || !result.Kind.Valid() {
		return internal()
	}
	if kind == OperationCreate || kind == OperationUpsert {
		if !result.Success && result.RecordID != "" {
			return internal()
		}
	} else if result.RecordID != expectedRecordID {
		return internal()
	}
	if result.Success {
		if len(result.Errors) != 0 || result.RecordID == "" {
			return internal()
		}
		if result.Kind == OperationCreate && (result.Created == nil || !*result.Created) {
			return internal()
		}
		if result.Kind == OperationUpsert && result.Created == nil {
			return internal()
		}
		if result.Kind != OperationCreate && result.Kind != OperationUpsert && result.Created != nil {
			return internal()
		}
	} else if len(result.Errors) == 0 || result.Created != nil {
		return internal()
	}
	if len(result.RecordID) > MaxRecordIdentifierBytes || !utf8.ValidString(result.RecordID) {
		return internal()
	}
	for _, operationError := range result.Errors {
		if !operationError.Kind.Valid() || !validAuthoredMetadata(operationError) ||
			!validCreateFromSourceGuard(kind, expectedObjectID, operationError) || !sort.StringsAreSorted(operationError.Fields) {
			return internal()
		}
		previous := ""
		for i, field := range operationError.Fields {
			if !validAPIIdentifier(field, MaxFieldIdentifierBytes) || (i > 0 && field == previous) {
				return internal()
			}
			previous = field
		}
	}
	return nil
}

func validCreateFromSourceGuard(kind OperationKind, expectedObjectID string, operationError OperationError) bool {
	details := operationError.CreateFromSourceGuard
	if details == nil {
		return true
	}
	if (kind != OperationCreate && kind != OperationUpsert) || operationError.Kind != ErrorInvalidOperation ||
		details.ObjectID != expectedObjectID ||
		!validAPIIdentifier(details.ObjectID, MaxObjectIdentifierBytes) ||
		len(details.ActionIDs) == 0 || !sort.StringsAreSorted(details.ActionIDs) {
		return false
	}
	previous := ""
	for index, actionID := range details.ActionIDs {
		if !validAPIIdentifier(actionID, MaxFieldIdentifierBytes) || (index > 0 && actionID == previous) {
			return false
		}
		previous = actionID
	}
	return true
}

func validAuthoredMetadata(operationError OperationError) bool {
	message := operationError.AuthoredMessage
	if message == "" {
		return true
	}
	if (operationError.Kind != ErrorCustomValidation && operationError.Kind != ErrorPolicyDenied) ||
		!utf8.ValidString(message) || strings.TrimSpace(message) == "" {
		return false
	}
	// Metadata-authored display text may contain any printable Unicode. Control
	// and format code points are excluded so the typed field cannot become a
	// log/header escape or invisible text channel at a transport boundary.
	for _, r := range message {
		if unicode.IsControl(r) || unicode.Is(unicode.Cf, r) {
			return false
		}
	}
	return true
}

// Kept private-name compatible for the original v1 tests while the accepted
// metadata-authored grammar now also covers policy denials.
func validAuthoredValidationMessage(operationError OperationError) bool {
	return validAuthoredMetadata(operationError)
}

func responseSize(value any) int {
	raw, err := json.Marshal(value)
	if err != nil {
		return MaxSemanticResponseBytes + 1
	}
	return len(raw)
}

func validUTCInstant(value string) bool {
	parsed, err := time.Parse(time.RFC3339Nano, value)
	return err == nil && parsed.Location() == time.UTC && strings.HasSuffix(value, "Z")
}

func validCommandDigest(value string) bool {
	if len(value) != len("sha256:")+64 || !strings.HasPrefix(value, "sha256:") {
		return false
	}
	for _, r := range value[len("sha256:"):] {
		if (r < '0' || r > '9') && (r < 'a' || r > 'f') {
			return false
		}
	}
	return true
}

func validAPIIdentifier(value string, maximum int) bool {
	return value != "" && utf8.ValidString(value) && len(value) <= maximum && apiIdentifierPattern.MatchString(value)
}

func contextFailure(ctx context.Context) error {
	if err := ctx.Err(); err != nil {
		if err == context.DeadlineExceeded {
			return &Failure{Category: FailureDeadlineExceeded}
		}
		return &Failure{Category: FailureCanceled}
	}
	return nil
}

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

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

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