package recordviewv1

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

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	permission "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	query "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
)

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

func SemanticReadRequestSize(request ReadRequest) (int, error) {
	encoded, err := json.Marshal(request)
	return len(encoded), err
}

func SemanticReadResponseSize(response ReadResponse) (int, error) {
	encoded, err := json.Marshal(response)
	return len(encoded), err
}

func SemanticResolveRequestSize(request ResolveAttachmentSourcesRequest) (int, error) {
	encoded, err := json.Marshal(request)
	return len(encoded), err
}

func SemanticResolveResponseSize(response ResolveAttachmentSourcesResponse) (int, error) {
	encoded, err := json.Marshal(response)
	return len(encoded), err
}

func ValidateReadRequest(ctx context.Context, request ReadRequest) error {
	if err := contextFailure(ctx); err != nil {
		return err
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(InvalidComponentDeadline)
	}
	if err := validateObjectID(request.ObjectID); err != nil {
		return err
	}
	if !request.Projection.Valid() {
		return invalid(InvalidComponentOperation)
	}
	if request.Authority != nil {
		if len(request.Authority.Sections) == 0 || len(request.Authority.Sections) > MaxAuthoritySections {
			return invalid(InvalidComponentOperation)
		}
		previous := -1
		for _, section := range request.Authority.Sections {
			order := authoritySectionOrder(section)
			if order <= previous {
				return invalid(InvalidComponentOperation)
			}
			previous = order
		}
	}
	if !request.Selector.Kind.Valid() {
		return invalid(InvalidComponentOperation)
	}
	switch request.Selector.Kind {
	case SelectorRecordID:
		if request.Selector.FieldID != "" || request.Selector.FieldValue != "" {
			return invalid(InvalidComponentOperation)
		}
		if err := validateRecordID(request.Selector.RecordID); err != nil {
			return err
		}
	case SelectorFieldEqual:
		if request.Selector.RecordID != "" {
			return invalid(InvalidComponentOperation)
		}
		if err := validateFieldID(request.Selector.FieldID); err != nil {
			return err
		}
		if !utf8.ValidString(request.Selector.FieldValue) {
			return invalid(InvalidComponentFieldValue)
		}
	}
	if rawReadRequestBytes(request) > MaxSemanticRequestBytes {
		return exhausted(LimitSemanticRequest, MaxSemanticRequestBytes)
	}
	size, err := SemanticReadRequestSize(request)
	if err != nil {
		return invalid(InvalidComponentResponse)
	}
	if size > MaxSemanticRequestBytes {
		return exhausted(LimitSemanticRequest, MaxSemanticRequestBytes)
	}
	return nil
}

func ValidateReadResponse(request ReadRequest, response ReadResponse) error {
	if !common.ValidReceipts(response.Receipts, CapabilityName) {
		return incompatible()
	}
	if err := validateRecordID(response.RecordID); err != nil {
		return internal()
	}
	if request.Selector.Kind == SelectorRecordID && response.RecordID != request.Selector.RecordID {
		return internal()
	}
	if !validIdentifier(response.Root.ObjectID, MaxObjectIdentifierBytes) {
		return internal()
	}
	if response.Root.ObjectID != request.ObjectID {
		return internal()
	}
	if response.Access != nil && !response.Access.Read {
		// A successful record-view response has already proven that the caller
		// may read Root. Publishing a contradictory access envelope would make
		// the L1 projection non-deterministic, so fail closed.
		return internal()
	}
	if (request.Authority == nil) != (response.Authority == nil) ||
		!validSelectedAuthority(request, response.Authority) {
		return internal()
	}
	if len(response.Root.Fields) > MaxRootFields {
		return exhausted(LimitFieldCount, MaxRootFields)
	}
	if err := query.ValidateRecord(response.Root); err != nil {
		var failure *common.Failure
		if errors.As(err, &failure) && failure.Category == FailureResourceExhausted {
			return failure
		}
		return internal()
	}
	if len(response.ReferenceDisplays) > MaxReferenceDisplays {
		return exhausted(LimitFieldCount, MaxReferenceDisplays)
	}
	if request.Projection != ProjectionEnrichedView && len(response.ReferenceDisplays) != 0 {
		return internal()
	}
	if len(response.AttachmentSourceFields) > MaxAttachmentSources ||
		(request.Projection != ProjectionEnrichedView && len(response.AttachmentSourceFields) != 0) {
		return internal()
	}
	if !sort.SliceIsSorted(response.ReferenceDisplays, func(i, j int) bool {
		return response.ReferenceDisplays[i].SourceFieldID < response.ReferenceDisplays[j].SourceFieldID
	}) {
		return internal()
	}
	rootFields := make(map[string]Value, len(response.Root.Fields))
	for _, field := range response.Root.Fields {
		rootFields[field.Name] = field.Value
	}
	previous := ""
	for _, display := range response.ReferenceDisplays {
		if display.SourceFieldID == previous || !validIdentifier(display.SourceFieldID, MaxFieldIdentifierBytes) ||
			!validIdentifier(display.RelationshipID, MaxFieldIdentifierBytes) ||
			!validIdentifier(display.TargetObjectID, MaxObjectIdentifierBytes) ||
			!validRecordIDText(display.TargetRecordID) || !utf8.ValidString(display.DisplayName) || display.DisplayName == "" {
			return internal()
		}
		value, ok := rootFields[display.SourceFieldID]
		if !ok || value.Null || value.Kind != ValueReference || value.Reference == nil || *value.Reference != display.TargetRecordID {
			return internal()
		}
		previous = display.SourceFieldID
	}
	if !sort.SliceIsSorted(response.AttachmentSourceFields, func(i, j int) bool {
		return response.AttachmentSourceFields[i].SourceFieldID < response.AttachmentSourceFields[j].SourceFieldID
	}) {
		return internal()
	}
	previous = ""
	for _, source := range response.AttachmentSourceFields {
		if source.SourceFieldID == previous || !validIdentifier(source.SourceFieldID, MaxFieldIdentifierBytes) ||
			!validIdentifier(source.ResolverID, MaxResolverIdentifierBytes) {
			return internal()
		}
		value, ok := rootFields[source.SourceFieldID]
		if !ok || value.Null || value.Kind != ValueString || value.String == nil {
			return internal()
		}
		previous = source.SourceFieldID
	}
	size, err := SemanticReadResponseSize(response)
	if err != nil {
		return internal()
	}
	expansion := query.SemanticRecordExpansionBytes(response.Root)
	if size > MaxSemanticResponseBytes || expansion > uint64(MaxSemanticResponseBytes-size) {
		return exhausted(LimitSemanticResponse, MaxSemanticResponseBytes)
	}
	return nil
}

func authoritySectionOrder(section AuthoritySection) int {
	switch section {
	case AuthoritySystemPermissions:
		return 0
	case AuthorityCustomPermissions:
		return 1
	case AuthorityObjectPermissions:
		return 2
	case AuthorityFieldPermissions:
		return 3
	case AuthorityRecordTypePermissions:
		return 4
	default:
		return -1
	}
}

func authoritySectionRequested(request ReadRequest, section AuthoritySection) bool {
	if request.Authority == nil {
		return false
	}
	for _, candidate := range request.Authority.Sections {
		if candidate == section {
			return true
		}
	}
	return false
}

func validSelectedAuthority(request ReadRequest, authority *SelectedAuthority) bool {
	if authority == nil {
		return request.Authority == nil
	}
	if (!authoritySectionRequested(request, AuthoritySystemPermissions) && len(authority.SystemPermissions) != 0) ||
		(!authoritySectionRequested(request, AuthorityCustomPermissions) && len(authority.CustomPermissions) != 0) ||
		(!authoritySectionRequested(request, AuthorityObjectPermissions) && len(authority.ObjectPermissions) != 0) ||
		(!authoritySectionRequested(request, AuthorityFieldPermissions) && len(authority.FieldPermissions) != 0) ||
		(!authoritySectionRequested(request, AuthorityRecordTypePermissions) && len(authority.RecordTypePermissions) != 0) {
		return false
	}
	if err := permission.ValidateAuthorityFacts(permission.Response{
		SystemPermissions: authority.SystemPermissions, CustomPermissions: authority.CustomPermissions,
		ObjectPermissions: authority.ObjectPermissions, FieldPermissions: authority.FieldPermissions,
		RecordTypePermissions: authority.RecordTypePermissions,
	}); err != nil || len(authority.ObjectPermissions) > 1 {
		return false
	}
	for _, permission := range authority.ObjectPermissions {
		if permission.ResourceType != request.ObjectID {
			return false
		}
	}
	for _, permission := range authority.FieldPermissions {
		if permission.ResourceType != request.ObjectID {
			return false
		}
	}
	for _, permission := range authority.RecordTypePermissions {
		if permission.ResourceType != request.ObjectID {
			return false
		}
	}
	return true
}

func ValidateResolveRequest(ctx context.Context, request ResolveAttachmentSourcesRequest) error {
	if err := contextFailure(ctx); err != nil {
		return err
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(InvalidComponentDeadline)
	}
	if err := validateObjectID(request.ParentObjectID); err != nil {
		return err
	}
	if err := validateRecordID(request.ParentRecordID); err != nil {
		return err
	}
	if !validIdentifier(request.ResolverID, MaxResolverIdentifierBytes) {
		if len(request.ResolverID) > MaxResolverIdentifierBytes {
			return exhausted(LimitFieldID, MaxResolverIdentifierBytes)
		}
		return invalid(InvalidComponentField)
	}
	if len(request.SourceValues) == 0 {
		return invalid(InvalidComponentOperation)
	}
	if len(request.SourceValues) > MaxAttachmentSources {
		return exhausted(LimitOperationCount, MaxAttachmentSources)
	}
	remaining := MaxSemanticRequestBytes - len(request.ParentObjectID) - len(request.ParentRecordID) - len(request.ResolverID)
	for _, sourceValue := range request.SourceValues {
		if sourceValue == "" || !utf8.ValidString(sourceValue) {
			return invalid(InvalidComponentFieldValue)
		}
		if len(sourceValue) > remaining {
			return exhausted(LimitSemanticRequest, MaxSemanticRequestBytes)
		}
		remaining -= len(sourceValue)
	}
	size, err := SemanticResolveRequestSize(request)
	if err != nil {
		return invalid(InvalidComponentResponse)
	}
	if size > MaxSemanticRequestBytes {
		return exhausted(LimitSemanticRequest, MaxSemanticRequestBytes)
	}
	return nil
}

func ValidateResolveResponse(request ResolveAttachmentSourcesRequest, response ResolveAttachmentSourcesResponse) error {
	if !common.ValidReceipts(response.Receipts, CapabilityName) {
		return incompatible()
	}
	if len(response.Results) != len(request.SourceValues) || len(response.Results) > MaxAttachmentSources {
		return internal()
	}
	for index, result := range response.Results {
		if result.Index != uint32(index) || !result.Outcome.Valid() {
			return internal()
		}
		switch result.Outcome {
		case AttachmentSourceResolved:
			if !validRecordIDText(result.FileID) || result.FileName == "" || !utf8.ValidString(result.FileName) {
				return internal()
			}
		default:
			if result.FileID != "" || result.FileName != "" {
				return internal()
			}
		}
	}
	size, err := SemanticResolveResponseSize(response)
	if err != nil {
		return internal()
	}
	if size > MaxSemanticResponseBytes {
		return exhausted(LimitSemanticResponse, MaxSemanticResponseBytes)
	}
	return nil
}

func rawReadRequestBytes(request ReadRequest) int {
	return len(request.ObjectID) + len(request.Selector.RecordID) + len(request.Selector.FieldID) + len(request.Selector.FieldValue)
}

func validateObjectID(value string) error {
	if len(value) > MaxObjectIdentifierBytes {
		return exhausted(LimitObjectID, MaxObjectIdentifierBytes)
	}
	if !validIdentifier(value, MaxObjectIdentifierBytes) {
		return invalid(InvalidComponentObjectID)
	}
	return nil
}

func validateFieldID(value string) error {
	if len(value) > MaxFieldIdentifierBytes {
		return exhausted(LimitFieldID, MaxFieldIdentifierBytes)
	}
	if !validIdentifier(value, MaxFieldIdentifierBytes) {
		return invalid(InvalidComponentField)
	}
	return nil
}

func validateRecordID(value string) error {
	if len(value) > MaxRecordIdentifierBytes {
		return exhausted(LimitRecordID, MaxRecordIdentifierBytes)
	}
	if !validRecordIDText(value) {
		return invalid(InvalidComponentRecordID)
	}
	return nil
}

func validIdentifier(value string, maximum int) bool {
	return value != "" && len(value) <= maximum && identifierPattern.MatchString(value)
}

func validRecordIDText(value string) bool {
	return value != "" && len(value) <= MaxRecordIdentifierBytes && utf8.ValidString(value) && strings.TrimSpace(value) == 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 incompatible() *Failure {
	return &Failure{Category: FailureIncompatible, RequiredCapability: CapabilityName, SupportedMajor: ProtocolMajor, SupportedMinorMin: ProtocolMinor, SupportedMinorMax: ProtocolMinor}
}

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