package layoutauthoringv2

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

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	authoring "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
	adminread "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/metadataadminreadv1"
)

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

func ValidateRequest(ctx context.Context, request Request) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	return validateRequestBody(request)
}

func validateRequestBody(request Request) error {
	if !common.ValidObjectIdentifier(request.ObjectID) || !revisionPattern.MatchString(request.ExpectedLayoutRevision) {
		return invalid(common.InvalidComponentCommandData)
	}
	if err := ValidateHistoryRelatedListProjections(request.ObjectID, request.HistoryRelatedLists); err != nil {
		return err
	}
	legacy, ok := validationOperation(request)
	if !ok {
		return invalid(common.InvalidComponentOperation)
	}
	if err := authoring.ValidateCommandOperationShape(legacy); err != nil {
		return err
	}
	encoded, _ := json.Marshal(request)
	if len(encoded) > MaxSemanticRequestBytes {
		return &common.Failure{Category: common.FailureResourceExhausted, LimitKind: common.LimitSemanticRequest, Maximum: MaxSemanticRequestBytes}
	}
	return nil
}

func ValidateWorkspaceRequest(ctx context.Context, request WorkspaceRequest) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	if !common.ValidObjectIdentifier(request.ObjectID) {
		return invalid(common.InvalidComponentOperation)
	}
	return nil
}

func ValidateWorkspaceResponse(request WorkspaceRequest, response WorkspaceResponse) error {
	if !revisionPattern.MatchString(response.LayoutRevision) ||
		!validReceipts(response.Receipts) {
		return internal()
	}
	// Reuse the established stored-workspace grammar and resource bounds. Only
	// the capability receipt differs between the read and authoring ports.
	adminReceipts := response.Receipts
	adminReceipts.CapabilityVersion = adminread.CapabilityName
	if err := adminread.ValidateLayoutAdminWorkspaceResponse(
		adminread.LayoutAdminWorkspaceRequest{
			ObjectID: request.ObjectID,
		},
		adminread.LayoutAdminWorkspaceResponse{
			ObjectID: response.ObjectID, Layouts: response.Layouts,
			Assignments: response.Assignments, Receipts: adminReceipts,
		},
	); err != nil {
		return err
	}
	return responseSize(response)
}

func ValidateStoredLayoutRequest(ctx context.Context, request StoredLayoutRequest) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	if !common.ValidObjectIdentifier(request.ObjectID) || !identifier(request.LayoutID) {
		return invalid(common.InvalidComponentOperation)
	}
	return nil
}

func ValidateStoredLayoutResponse(request StoredLayoutRequest, response StoredLayoutResponse) error {
	if response.LayoutID != request.LayoutID || !identifier(response.LayoutID) ||
		response.ObjectID != request.ObjectID || !common.ValidObjectIdentifier(response.ObjectID) || !revisionPattern.MatchString(response.LayoutRevision) ||
		response.Document.LayoutID != response.LayoutID || response.Document.ObjectID != response.ObjectID ||
		!validReceipts(response.Receipts) {
		return internal()
	}
	if err := authoring.ValidateLayoutDocument(response.Document); err != nil {
		return internal()
	}
	if err := authoring.ValidateOrderedListFields(response.OrderedListFields); err != nil {
		return internal()
	}
	if err := authoring.ValidateCreateContext(authoring.CreateContext{RequiredFields: response.RequiredCreateFields}); err != nil {
		return internal()
	}
	return responseSize(response)
}

func ValidateResponse(request Request, response Response) error {
	if err := validateRequestBody(request); err != nil {
		return internal()
	}
	if !revisionPattern.MatchString(response.LayoutRevision) ||
		!common.ValidReceipts(response.Receipts, CapabilityName) {
		return internal()
	}
	if response.Changed == (response.LayoutRevision == request.ExpectedLayoutRevision) ||
		!responseIDsMatchRequest(request, response) {
		return internal()
	}
	if response.LayoutID != nil && !identifier(*response.LayoutID) ||
		response.AssignmentID != nil && !identifier(*response.AssignmentID) {
		return internal()
	}
	encoded, _ := json.Marshal(response)
	if len(encoded) > MaxSemanticResponseBytes {
		return internal()
	}
	return nil
}

func responseIDsMatchRequest(request Request, response Response) bool {
	if operationCount(request.Operation) != 1 {
		return false
	}
	layoutID, assignmentID := response.LayoutID, response.AssignmentID
	matchLayoutOnly := func(expected string) bool {
		return layoutID != nil && *layoutID == expected && assignmentID == nil
	}
	switch operation := request.Operation; {
	case operation.Create != nil:
		return matchLayoutOnly(operation.Create.Document.LayoutID)
	case operation.Update != nil:
		return matchLayoutOnly(operation.Update.LayoutID)
	case operation.Clone != nil:
		return matchLayoutOnly(operation.Clone.TargetLayoutID)
	case operation.SetDefault != nil:
		return matchLayoutOnly(operation.SetDefault.LayoutID)
	case operation.Delete != nil:
		return matchLayoutOnly(operation.Delete.LayoutID)
	case operation.Restore != nil:
		return matchLayoutOnly(operation.Restore.LayoutID)
	case operation.AssignmentUpsert != nil:
		return layoutID != nil && *layoutID == operation.AssignmentUpsert.LayoutID && assignmentID != nil
	default:
		return layoutID == nil && assignmentID != nil &&
			*assignmentID == operation.AssignmentDelete.AssignmentID
	}
}

func validReceipts(receipts Receipts) bool {
	return common.ValidReceipts(receipts, CapabilityName)
}

func responseSize(response any) error {
	encoded, _ := json.Marshal(response)
	if len(encoded) > MaxSemanticResponseBytes {
		return internal()
	}
	return nil
}

func validationOperation(request Request) (authoring.CommandOperation, bool) {
	revision := request.ExpectedLayoutRevision
	op := request.Operation
	if operationCount(op) != 1 {
		return authoring.CommandOperation{}, false
	}
	switch {
	case op.Create != nil:
		if op.Create.Document.ObjectID != request.ObjectID {
			return authoring.CommandOperation{}, false
		}
		var expected *string
		if op.Create.ListFields.Mode != authoring.ChangePreserve {
			expected = &revision
		}
		return authoring.CommandOperation{Create: &authoring.CreateLayout{Document: op.Create.Document, ListFields: op.Create.ListFields, ExpectedWorkspaceVersion: expected}}, true
	case op.Update != nil:
		return authoring.CommandOperation{Update: &authoring.UpdateLayout{LayoutID: op.Update.LayoutID, ExpectedAggregateVersion: revision, Changes: op.Update.Changes, ListFields: op.Update.ListFields}}, true
	case op.Clone != nil:
		return authoring.CommandOperation{Clone: &authoring.CloneLayout{SourceLayoutID: op.Clone.SourceLayoutID, SourceResourceVersion: revision, TargetLayoutID: op.Clone.TargetLayoutID, TargetName: op.Clone.TargetName, ExpectedWorkspaceVersion: revision}}, true
	case op.SetDefault != nil:
		return authoring.CommandOperation{SetDefault: &authoring.SetDefaultLayout{ObjectID: request.ObjectID, LayoutID: op.SetDefault.LayoutID, ExpectedWorkspaceVersion: revision}}, true
	case op.Delete != nil:
		return authoring.CommandOperation{Delete: &authoring.DeleteLayout{LayoutID: op.Delete.LayoutID, ExpectedResourceVersion: revision, ExpectedWorkspaceVersion: revision}}, true
	case op.Restore != nil:
		return authoring.CommandOperation{Restore: &authoring.RestoreLayout{LayoutID: op.Restore.LayoutID, HistoryVersion: op.Restore.HistoryVersion, ExpectedResourceVersion: revision}}, true
	case op.AssignmentUpsert != nil:
		return authoring.CommandOperation{AssignmentUpsert: &authoring.UpsertLayoutAssignment{ObjectID: request.ObjectID, LayoutID: op.AssignmentUpsert.LayoutID, Selectors: op.AssignmentUpsert.Selectors, ExpectedWorkspaceVersion: revision}}, true
	default:
		return authoring.CommandOperation{AssignmentDelete: &authoring.DeleteLayoutAssignment{AssignmentID: op.AssignmentDelete.AssignmentID, ExpectedResourceVersion: revision, ExpectedWorkspaceVersion: revision}}, true
	}
}

func operationCount(operation Operation) int {
	count := 0
	for _, present := range []bool{operation.Create != nil, operation.Update != nil, operation.Clone != nil, operation.SetDefault != nil, operation.Delete != nil, operation.Restore != nil, operation.AssignmentUpsert != nil, operation.AssignmentDelete != nil} {
		if present {
			count++
		}
	}
	return count
}

func identifier(value string) bool {
	if value == "" || len(value) > authoring.MaxOpaqueIdentifierBytes || !utf8.ValidString(value) || strings.TrimSpace(value) != value {
		return false
	}
	for _, r := range value {
		if unicode.IsControl(r) || unicode.Is(unicode.Cf, r) ||
			unicode.Is(unicode.Zl, r) || unicode.Is(unicode.Zp, r) {
			return false
		}
	}
	return true
}

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