package approvalv1

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

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

func ValidateExecuteRequest(ctx context.Context, request ExecuteRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !validExecuteRequestShape(request) {
		return invalid(common.InvalidComponentCommandData)
	}
	return validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest)
}

func validExecuteRequestShape(request ExecuteRequest) bool {
	if !request.Operation.Valid() || !validOpaque(request.IdempotencyKey) || !validComment(request.Comments) {
		return false
	}
	switch request.Operation {
	case OperationSubmit:
		if (request.ObjectID != "" && !common.ValidObjectIdentifier(request.ObjectID)) || !validOpaque(request.TargetRecordID) ||
			request.WorkItemID != "" || request.NextActorID != "" {
			return false
		}
	case OperationApprove, OperationReject, OperationRecall:
		if !validOpaque(request.WorkItemID) || request.ObjectID != "" || request.TargetRecordID != "" ||
			request.NextActorID != "" || request.SkipEntryCriteria {
			return false
		}
	case OperationReassign:
		if !validOpaque(request.WorkItemID) || !validOpaque(request.NextActorID) || request.ObjectID != "" ||
			request.TargetRecordID != "" || request.Comments != "" || request.SkipEntryCriteria {
			return false
		}
	}
	return true
}

func ValidateExecuteResponse(request ExecuteRequest, response ExecuteResponse) error {
	if !validExecuteRequestShape(request) || validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest) != nil ||
		!common.ValidReceipts(response.Receipts, ExecuteCapabilityName) ||
		!validOpaque(response.TargetRecordID) ||
		(response.ActingPrincipalID != "" && !validOpaque(response.ActingPrincipalID)) ||
		len(response.ActionFailures) > MaxActionFailures {
		return internal()
	}
	if response.SubmitRejection != nil {
		if request.Operation != OperationSubmit || response.TargetRecordID != request.TargetRecordID ||
			!response.SubmitRejection.Reason.Valid() || response.ExecutionID != "" ||
			(response.SubmitRejection.Reason == SubmitRejectionEntryCriteriaMiss && request.SkipEntryCriteria) ||
			!validSubmitRejection(*response.SubmitRejection) ||
			response.InstanceID != "" || response.SourceWorkItemID != "" ||
			response.ActingPrincipalID != "" || response.State != "" ||
			len(response.PendingWorkItems) != 0 || len(response.ActionFailures) != 0 {
			return internal()
		}
		return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
	}
	if !validOpaque(response.ExecutionID) || !validOpaque(response.InstanceID) || !response.State.Valid() {
		return internal()
	}
	if request.Operation == OperationSubmit {
		if response.SourceWorkItemID != "" || response.TargetRecordID != request.TargetRecordID {
			return internal()
		}
	} else if response.SourceWorkItemID != request.WorkItemID {
		return internal()
	}
	if !validStateForOperation(request.Operation, response.State) {
		return internal()
	}
	if response.State == InstanceStatePending {
		if !validWorkItemAssignments(response.PendingWorkItems) {
			return internal()
		}
	} else if len(response.PendingWorkItems) != 0 {
		return internal()
	}
	for _, failure := range response.ActionFailures {
		if !validActionFailure(failure) {
			return internal()
		}
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

func validSubmitRejection(rejection SubmitRejection) bool {
	if rejection.Reason != SubmitRejectionRecordValidationFailed {
		return len(rejection.ValidationIssues) == 0
	}
	if len(rejection.ValidationIssues) == 0 || len(rejection.ValidationIssues) > MaxValidationIssues {
		return false
	}
	for _, issue := range rejection.ValidationIssues {
		if !validOpaque(issue.RuleName) ||
			(issue.Field != "" && !common.ValidFieldIdentifier(issue.Field)) ||
			!validSafeText(issue.Message, MaxDisplayTextBytes, true) {
			return false
		}
	}
	return true
}

func ValidateReadRequest(ctx context.Context, request ReadRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if !validReadRequestShape(request) {
		if !request.Kind.Valid() {
			return invalid(common.InvalidComponentOperation)
		}
		return invalid(common.InvalidComponentRecordID)
	}
	return validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest)
}

func validReadRequestShape(request ReadRequest) bool {
	if !request.Kind.Valid() {
		return false
	}
	if request.Kind == ReadPendingForCaller {
		return request.TargetRecordID == ""
	}
	return validOpaque(request.TargetRecordID)
}

func ValidateReadResponse(request ReadRequest, response ReadResponse) error {
	if !validReadRequestShape(request) || validateSize(request, MaxSemanticRequestBytes, common.LimitSemanticRequest) != nil ||
		!common.ValidReceipts(response.Receipts, ReadCapabilityName) {
		return internal()
	}
	switch request.Kind {
	case ReadPendingForCaller:
		if response.Latest != nil || len(response.Steps) != 0 || len(response.Pending) > MaxPendingItems {
			return internal()
		}
		seen := make(map[string]struct{}, len(response.Pending))
		for _, item := range response.Pending {
			if !validPendingItem(item) {
				return internal()
			}
			if _, exists := seen[item.WorkItemID]; exists {
				return internal()
			}
			seen[item.WorkItemID] = struct{}{}
		}
	case ReadLatestForRecord:
		if len(response.Pending) != 0 || len(response.Steps) != 0 ||
			(response.Latest != nil && !validInstanceSummary(*response.Latest, request.TargetRecordID)) {
			return internal()
		}
	case ReadStepDisplayForRecord:
		if len(response.Pending) != 0 || response.Latest != nil || len(response.Steps) > MaxStepDisplayItems {
			return internal()
		}
		seen := make(map[string]struct{}, len(response.Steps))
		var previous uint32
		for index, step := range response.Steps {
			if !validStepDisplay(step) || (index > 0 && step.StepOrder <= previous) {
				return internal()
			}
			if _, exists := seen[step.StepID]; exists {
				return internal()
			}
			seen[step.StepID] = struct{}{}
			previous = step.StepOrder
		}
	default:
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, common.LimitSemanticResponse)
}

func validStateForOperation(operation Operation, state InstanceState) bool {
	switch operation {
	case OperationSubmit, OperationApprove:
		return state == InstanceStatePending || state == InstanceStateApproved || state == InstanceStateRejected
	case OperationReject:
		return state == InstanceStatePending || state == InstanceStateRejected
	case OperationRecall:
		return state == InstanceStateWithdrawn
	case OperationReassign:
		return state == InstanceStatePending
	default:
		return false
	}
}

func validActionFailure(failure ActionFailure) bool {
	return validOpaque(failure.ActionRef) && failure.Kind.Valid() && failure.Reason.Valid()
}

func validWorkItemAssignments(values []WorkItemAssignment) bool {
	if len(values) == 0 || len(values) > MaxWorkItemIDs || len(values) > MaxActorIDs {
		return false
	}
	workItems := make(map[string]struct{}, len(values))
	actors := make(map[string]struct{}, len(values))
	for _, value := range values {
		if !validOpaque(value.WorkItemID) || !validOpaque(value.ActorID) {
			return false
		}
		if _, exists := workItems[value.WorkItemID]; exists {
			return false
		}
		if _, exists := actors[value.ActorID]; exists {
			return false
		}
		workItems[value.WorkItemID] = struct{}{}
		actors[value.ActorID] = struct{}{}
	}
	return true
}

func validPendingItem(item PendingItem) bool {
	return validOpaque(item.WorkItemID) && validOpaque(item.InstanceID) &&
		validOpaque(item.DefinitionID) && validOpaque(item.TargetRecordID) &&
		common.ValidObjectIdentifier(item.ObjectID) && validOpaque(item.SubmittedByID) &&
		validOptionalDisplay(item.SubmittedByName) && validDateTime(item.SubmittedAt) &&
		validOpaque(item.ActorID) && (item.OriginalActorID == "" || validOpaque(item.OriginalActorID)) &&
		validComment(item.Comments) && validOptionalDisplay(item.RecordName)
}

func validInstanceSummary(summary InstanceSummary, requestedRecordID string) bool {
	if !validOpaque(summary.InstanceID) || !validOpaque(summary.DefinitionID) ||
		!validOpaque(summary.TargetRecordID) || summary.TargetRecordID != requestedRecordID ||
		!summary.State.Valid() || !validOpaque(summary.SubmittedByID) ||
		!validOptionalDisplay(summary.SubmittedByName) || !validDateTime(summary.CreatedAt) ||
		(summary.CompletedAt != "" && !validDateTime(summary.CompletedAt)) ||
		len(summary.PendingWorkItems) > MaxWorkItemIDs {
		return false
	}
	if summary.State == InstanceStatePending && summary.CompletedAt != "" {
		return false
	}
	if summary.State != InstanceStatePending && summary.CompletedAt == "" {
		return false
	}
	seen := make(map[string]struct{}, len(summary.PendingWorkItems))
	for _, item := range summary.PendingWorkItems {
		if !validOpaque(item.WorkItemID) || !validOpaque(item.ActorID) ||
			!validOptionalDisplay(item.ActorName) ||
			(item.OriginalActorID != "" && !validOpaque(item.OriginalActorID)) ||
			!validOptionalDisplay(item.OriginalActorName) {
			return false
		}
		if _, exists := seen[item.WorkItemID]; exists {
			return false
		}
		seen[item.WorkItemID] = struct{}{}
	}
	if summary.State == InstanceStatePending {
		return len(summary.PendingWorkItems) > 0
	}
	return len(summary.PendingWorkItems) == 0
}

func validStepDisplay(step StepDisplay) bool {
	if !validOpaque(step.StepID) || step.StepOrder == 0 ||
		!validOptionalDisplay(step.StepName) || !validOptionalDisplay(step.StepLabel) {
		return false
	}
	if step.HasCriteria {
		return step.NoMatchBehavior.Valid()
	}
	return step.NoMatchBehavior == ""
}

func validOpaque(value string) bool {
	return validSafeText(value, MaxIdentifierBytes, false) && strings.TrimSpace(value) == value
}

func validOptionalDisplay(value string) bool {
	return value == "" || validSafeText(value, MaxDisplayTextBytes, true)
}

func validComment(value string) bool {
	return value == "" || validSafeText(value, MaxCommentsBytes, true)
}

func validSafeText(value string, maximum int, allowLineBreaks bool) bool {
	if value == "" || len(value) > maximum || !utf8.ValidString(value) {
		return false
	}
	for _, r := range value {
		if r == 0 || (unicode.IsControl(r) && !(allowLineBreaks && (r == '\n' || r == '\r' || r == '\t'))) {
			return false
		}
	}
	return true
}

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

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

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