package automation

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/nexuscrm/kernel/pkg/perftiming"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func flowRecordLookupCacheKey(
	ctx context.Context,
	interview *models.FlowInterview,
	lookup *models.FlowRecordLookup,
	user *models.UserSession,
	filter string,
	queryLimit int,
) (string, bool, string) {
	if lookup == nil {
		return "", false, "nil_lookup"
	}
	objectName := strings.TrimSpace(lookup.Object)
	if objectName == "" {
		return "", false, "empty_object"
	}
	if !isBeforeSaveFlow(ctx) {
		return "", false, "not_before_save"
	}
	if isPreviewMode(ctx) {
		return "", false, "preview_mode"
	}
	if constants.HasStandardActionInternalAccessScopes(ctx) {
		return "", false, "standard_action_internal_scope"
	}
	if len(atomicPostUpsertLookupRecordIDs(ctx, interview, lookup)) != 0 {
		return "", false, "post_upsert_overlay"
	}

	var b strings.Builder
	appendLookupCacheKeyPart(&b, "v1")
	appendLookupCacheKeyPart(&b, strings.ToLower(objectName))
	appendLookupCacheKeyPart(&b, filter)
	appendLookupCacheKeyPart(&b, strings.TrimSpace(lookup.SortField))
	appendLookupCacheKeyPart(&b, strings.TrimSpace(lookup.SortOrder))
	appendLookupCacheKeyPart(&b, flowRecordLookupProjectionKey(lookup))
	appendLookupCacheKeyPart(&b, strconv.Itoa(queryLimit))
	appendLookupCacheKeyPart(&b, strconv.FormatBool(lookup.GetFirstRecordOnly))
	appendLookupCacheKeyPart(&b, strconv.FormatBool(isCrossObjectLookup(interview, objectName)))
	appendLookupCacheKeyPart(&b, fmt.Sprintf("%v", constants.GetSecurityContext(ctx)))
	appendLookupCacheKeyPart(&b, flowRecordLookupCacheUserKey(user))
	return b.String(), true, ""
}

func flowRecordLookupInFlightCoalesceKey(
	ctx context.Context,
	interview *models.FlowInterview,
	lookup *models.FlowRecordLookup,
	user *models.UserSession,
	filter string,
	queryLimit int,
) (string, bool, string) {
	if lookup == nil {
		return "", false, "nil_lookup"
	}
	if !lookup.CoalesceInFlight {
		return "", false, "not_enabled"
	}
	objectName := strings.TrimSpace(lookup.Object)
	if objectName == "" {
		return "", false, "empty_object"
	}
	if ctx == nil {
		return "", false, "missing_context"
	}
	if isPreviewMode(ctx) {
		return "", false, "preview_mode"
	}
	if constants.HasStandardActionInternalAccessScopes(ctx) {
		return "", false, "standard_action_internal_scope"
	}
	if len(atomicPostUpsertLookupRecordIDs(ctx, interview, lookup)) != 0 {
		return "", false, "post_upsert_overlay"
	}

	var b strings.Builder
	appendLookupCacheKeyPart(&b, "coalesce-v1")
	appendLookupCacheKeyPart(&b, strings.ToLower(objectName))
	appendLookupCacheKeyPart(&b, filter)
	appendLookupCacheKeyPart(&b, strings.TrimSpace(lookup.SortField))
	appendLookupCacheKeyPart(&b, strings.TrimSpace(lookup.SortOrder))
	appendLookupCacheKeyPart(&b, flowRecordLookupProjectionKey(lookup))
	appendLookupCacheKeyPart(&b, strconv.Itoa(queryLimit))
	appendLookupCacheKeyPart(&b, strconv.FormatBool(lookup.GetFirstRecordOnly))
	appendLookupCacheKeyPart(&b, fmt.Sprintf("%v", constants.GetSecurityContext(ctx)))
	appendLookupCacheKeyPart(&b, flowRecordLookupCacheUserKey(user))
	return b.String(), true, ""
}

func appendLookupCacheKeyPart(b *strings.Builder, part string) {
	b.WriteString(strconv.Itoa(len(part)))
	b.WriteByte(':')
	b.WriteString(part)
	b.WriteByte('|')
}

func flowRecordLookupCacheUserKey(user *models.UserSession) string {
	if user == nil {
		return "<nil>"
	}
	var b strings.Builder
	appendLookupCacheKeyPart(&b, strings.TrimSpace(user.ID))
	appendLookupCacheKeyPart(&b, strings.TrimSpace(user.ProfileID))
	appendLookupCacheKeyPart(&b, strconv.FormatBool(user.IsSystemAdmin))
	appendLookupCacheKeyPart(&b, strings.TrimSpace(user.ConnectedAppId))
	appendLookupCacheKeyPart(&b, strings.TrimSpace(user.Scope))
	appendLookupCacheKeyPart(&b, strings.TrimSpace(user.GrantType))
	appendLookupCacheKeyPart(&b, strings.TrimSpace(user.SessionClass))
	return b.String()
}

func logFlowRecordLookupCache(ctx context.Context, lookup *models.FlowRecordLookup, status, reason string, queryLimit int) {
	if !perftiming.Enabled(ctx) {
		return
	}
	objectName := ""
	elementName := ""
	if lookup != nil {
		objectName = lookup.Object
		elementName = lookup.Name
	}
	perftiming.BaseEvent(ctx, "flow_record_lookup_cache", status, nil).
		Str("element_name", elementName).
		Str("object", objectName).
		Str("reason", reason).
		Int("query_limit", queryLimit).
		Msg("Flow RecordLookup cache")
}
