package automation

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
	"time"

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

func logFlowTiming(ctx context.Context, flowName, versionID, elementName, elementType, objectName, status, nextElement string, started time.Time, err error) {
	if !perftiming.Enabled(ctx) {
		return
	}
	perftiming.InfoEventWithStatus(ctx, "flow_timing", started, status, err).
		Str("flow_name", flowName).
		Str("flow_version_id", versionID).
		Str("element_name", elementName).
		Str("element_type", elementType).
		Str("object", objectName).
		Str("next_element", nextElement).
		Bool("has_error", err != nil).
		Msg("Flow timing")
}

func logFlowVersionResolveTiming(ctx context.Context, flowName, versionID, definitionID, stage, status string, started time.Time, err error) {
	if !perftiming.Enabled(ctx) {
		return
	}
	perftiming.InfoEventWithStatus(ctx, "flow_version_resolve_timing", started, status, err).
		Str("flow_name", flowName).
		Str("flow_version_id", versionID).
		Str("flow_definition_id", definitionID).
		Str("stage", stage).
		Bool("has_error", err != nil).
		Msg("Flow version resolve timing")
}

func logFlowTriggerStageTiming(ctx context.Context, eventType, objectName, stage string, started time.Time, err error, matchedFlows, runnableFlows int) {
	if !perftiming.Enabled(ctx) {
		return
	}
	perftiming.InfoEventWithStatus(ctx, "flow_trigger_stage_timing", started, perftiming.Status(err), err).
		Str("event_type", eventType).
		Str("object", objectName).
		Str("stage", stage).
		Bool("has_error", err != nil).
		Int("matched_flows", matchedFlows).
		Int("runnable_flows", runnableFlows).
		Msg("Flow trigger stage timing")
}

func logFlowRecordLookupProfile(ctx context.Context, lookup flowRecordLookupProfileEvent) {
	if !perftiming.Enabled(ctx) {
		return
	}
	perftiming.InfoEventWithStatus(ctx, "flow_record_lookup_profile", lookup.Started, lookup.Status, lookup.Err).
		Str("flow_name", lookup.FlowName).
		Str("flow_version_id", lookup.FlowVersionID).
		Str("element_name", lookup.ElementName).
		Str("object", lookup.ObjectName).
		Str("source", lookup.Source).
		Str("filter_shape", lookup.FilterShape).
		Str("filter_hash", lookup.FilterHash).
		Str("batch_key_field", lookup.BatchKeyField).
		Str("batchable_reason", lookup.BatchableReason).
		Str("batch_skip_reason", lookup.BatchSkipReason).
		Bool("batchable", lookup.Batchable).
		Bool("batch_detached_tx", lookup.BatchDetachedTx).
		Bool("batch_key_field_found", lookup.BatchKeyFieldFound).
		Bool("batch_key_field_unique", lookup.BatchKeyFieldUnique).
		Bool("get_first_record_only", lookup.GetFirstRecordOnly).
		Bool("cross_object", lookup.CrossObject).
		Int("query_limit", lookup.QueryLimit).
		Int("rows", lookup.Rows).
		Bool("has_error", lookup.Err != nil).
		Msg("Flow RecordLookup profile")
}

type flowRecordLookupProfileEvent struct {
	Started             time.Time
	FlowName            string
	FlowVersionID       string
	ElementName         string
	ObjectName          string
	Status              string
	Source              string
	FilterShape         string
	FilterHash          string
	BatchKeyField       string
	BatchableReason     string
	BatchSkipReason     string
	Batchable           bool
	BatchDetachedTx     bool
	BatchKeyFieldFound  bool
	BatchKeyFieldUnique bool
	GetFirstRecordOnly  bool
	CrossObject         bool
	QueryLimit          int
	Rows                int
	Err                 error
}

type flowRecordLookupProfilePlan struct {
	shape       string
	batchable   bool
	reason      string
	keyField    string
	crossObject bool
}

func buildFlowRecordLookupProfilePlan(interview *models.FlowInterview, lookup *models.FlowRecordLookup) flowRecordLookupProfilePlan {
	if lookup == nil {
		return flowRecordLookupProfilePlan{reason: "nil_lookup"}
	}
	parts := []string{
		"object=" + strings.ToLower(strings.TrimSpace(lookup.Object)),
		"first=" + strconv.FormatBool(lookup.GetFirstRecordOnly),
		"sort=" + strings.ToLower(strings.TrimSpace(lookup.SortField)) + ":" + strings.ToLower(strings.TrimSpace(lookup.SortOrder)),
		"logic=" + strings.ToLower(strings.TrimSpace(lookup.FilterLogic)),
	}
	for _, filter := range lookup.Filters {
		parts = append(parts,
			strings.ToLower(strings.TrimSpace(filter.Field))+
				":"+strings.ToUpper(strings.TrimSpace(normalizeFlowOperator(filter.Operator)))+
				":"+flowRecordLookupValueShape(filter.Value),
		)
	}

	crossObject := true
	if interview != nil {
		crossObject = isCrossObjectLookup(interview, lookup.Object)
	}
	plan := flowRecordLookupProfilePlan{
		shape:       strings.Join(parts, "|"),
		crossObject: crossObject,
	}
	if strings.TrimSpace(lookup.Object) == "" {
		plan.reason = "empty_object"
		return plan
	}
	if strings.TrimSpace(lookup.SortField) != "" || strings.TrimSpace(lookup.SortOrder) != "" {
		plan.reason = "sort"
		return plan
	}
	logic := strings.ToLower(strings.TrimSpace(lookup.FilterLogic))
	if logic != "" && logic != "and" {
		plan.reason = "filter_logic"
		return plan
	}
	if len(lookup.Filters) == 0 {
		plan.reason = "no_filters"
		return plan
	}
	dynamicEq := 0
	for _, filter := range lookup.Filters {
		if err := validateIdentifier(filter.Field); err != nil {
			plan.reason = "invalid_field"
			return plan
		}
		operator := strings.ToUpper(strings.TrimSpace(normalizeFlowOperator(filter.Operator)))
		isEq := operator == "EQUALTO" || operator == "=" || operator == "=="
		isNeq := operator == "NOTEQUALTO" || operator == "!=" || operator == "<>"
		if flowRecordLookupValueIsDynamicReference(filter.Value) {
			if !isEq {
				plan.reason = "dynamic_non_eq"
				return plan
			}
			dynamicEq++
			plan.keyField = strings.TrimSpace(filter.Field)
			continue
		}
		if !isEq && !isNeq {
			plan.reason = "static_unsupported_operator"
			return plan
		}
	}
	switch dynamicEq {
	case 0:
		plan.reason = "no_dynamic_eq"
	case 1:
		plan.batchable = true
		plan.reason = "ok"
	default:
		plan.reason = "multiple_dynamic_eq"
	}
	return plan
}

func flowRecordLookupValueIsDynamicReference(value interface{}) bool {
	m, ok := value.(map[string]interface{})
	if !ok {
		return false
	}
	ref, ok := m["elementReference"]
	if !ok || ref == nil {
		return false
	}
	return strings.TrimSpace(fmt.Sprint(ref)) != ""
}

func flowRecordLookupValueShape(value interface{}) string {
	if m, ok := value.(map[string]interface{}); ok {
		if ref, ok := m["elementReference"]; ok && ref != nil {
			return "ref:" + strings.ToLower(strings.TrimSpace(fmt.Sprint(ref)))
		}
		if typed, ok := flowRecordLookupTypedLiteral(m); ok {
			return flowRecordLookupLiteralShape(typed)
		}
		return "map:" + shortTimingHash(fmt.Sprint(m))
	}
	return flowRecordLookupLiteralShape(value)
}

func flowRecordLookupTypedLiteral(m map[string]interface{}) (interface{}, bool) {
	for _, key := range []string{"stringValue", "numberValue", "booleanValue", "dateValue", "dateTimeValue"} {
		if val, exists := m[key]; exists && val != nil {
			return val, true
		}
	}
	return nil, false
}

func flowRecordLookupLiteralShape(value interface{}) string {
	switch v := value.(type) {
	case nil:
		return "literal:nil"
	case bool:
		return "literal:bool:" + strconv.FormatBool(v)
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
		return "literal:number:" + shortTimingHash(fmt.Sprint(v))
	case string:
		return "literal:string:" + shortTimingHash(v)
	default:
		return fmt.Sprintf("literal:%T:%s", value, shortTimingHash(fmt.Sprint(value)))
	}
}

func shortTimingHash(value string) string {
	sum := sha256.Sum256([]byte(value))
	return hex.EncodeToString(sum[:])[:12]
}

func flowElementTimingDescriptor(flowMeta *models.FlowMetadata, elementName string) (string, string) {
	if flowMeta == nil {
		return "", ""
	}
	for i := range flowMeta.Decisions {
		if flowMeta.Decisions[i].Name == elementName {
			return "Decision", ""
		}
	}
	for i := range flowMeta.Assignments {
		if flowMeta.Assignments[i].Name == elementName {
			return "Assignment", ""
		}
	}
	for i := range flowMeta.RecordCreates {
		if flowMeta.RecordCreates[i].Name == elementName {
			return "RecordCreate", flowMeta.RecordCreates[i].Object
		}
	}
	for i := range flowMeta.RecordUpdates {
		if flowMeta.RecordUpdates[i].Name == elementName {
			return "RecordUpdate", flowMeta.RecordUpdates[i].Object
		}
	}
	for i := range flowMeta.RecordDeletes {
		if flowMeta.RecordDeletes[i].Name == elementName {
			return "RecordDelete", flowMeta.RecordDeletes[i].Object
		}
	}
	for i := range flowMeta.RecordLookups {
		if flowMeta.RecordLookups[i].Name == elementName {
			return "RecordLookup", flowMeta.RecordLookups[i].Object
		}
	}
	for i := range flowMeta.ActionCalls {
		if flowMeta.ActionCalls[i].Name == elementName {
			return "ActionCall", flowMeta.ActionCalls[i].ActionType
		}
	}
	for i := range flowMeta.Subflows {
		if flowMeta.Subflows[i].Name == elementName {
			return "Subflow", flowMeta.Subflows[i].FlowName
		}
	}
	for i := range flowMeta.Loops {
		if flowMeta.Loops[i].Name == elementName {
			return "Loop", ""
		}
	}
	for i := range flowMeta.CustomErrors {
		if flowMeta.CustomErrors[i].Name == elementName {
			return "CustomError", ""
		}
	}
	for i := range flowMeta.Waits {
		if flowMeta.Waits[i].Name == elementName {
			return "Wait", ""
		}
	}
	return "Unknown", ""
}
