package automation

import (
	"context"
	"fmt"
	"sort"
	"strings"

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

type previewSyntheticLookupProjector interface {
	ProjectPreviewSyntheticRecords(ctx context.Context, objectName string, records []models.SObject, currentUser *models.UserSession) ([]models.SObject, error)
}

func (e *FlowExecutor) previewSyntheticLookupOverlayAvailable() bool {
	if e == nil || e.query == nil {
		return false
	}
	_, ok := e.query.(previewSyntheticLookupProjector)
	return ok
}

func (e *FlowExecutor) mergePreviewSyntheticLookupRecords(ctx context.Context, interview *models.FlowInterview, lookup *models.FlowRecordLookup, user *models.UserSession, result *models.QueryResult, limit int) (*models.QueryResult, error) {
	if lookup == nil {
		return result, nil
	}
	synthetic := constants.PreviewSyntheticRecordsForObject(ctx, lookup.Object)
	if len(synthetic) == 0 {
		return result, nil
	}
	projector, ok := e.query.(previewSyntheticLookupProjector)
	if !ok {
		return result, nil
	}
	if result == nil {
		result = &models.QueryResult{Done: true}
	}

	rows := make([]models.SObject, 0, len(result.Records)+len(synthetic))
	seenIDs := map[string]int{}
	for _, record := range result.Records {
		row := clonePreviewLookupRow(record)
		rows = append(rows, row)
		if id, _ := record[constants.FieldID].(string); id != "" {
			seenIDs[id] = len(rows) - 1
		}
	}

	rawSyntheticRows := make([]models.SObject, 0, len(synthetic))
	for _, record := range synthetic {
		rawSyntheticRows = append(rawSyntheticRows, models.SObject(record.Fields))
	}
	projectedSyntheticRows, err := projector.ProjectPreviewSyntheticRecords(ctx, lookup.Object, rawSyntheticRows, user)
	if err != nil {
		return nil, err
	}

	appendedSynthetic := 0
	changedRows := false
	for _, row := range projectedSyntheticRows {
		matches, err := e.previewSyntheticLookupRecordMatches(ctx, interview, lookup, user, row)
		if err != nil {
			return nil, err
		}
		if !matches {
			continue
		}
		if id, _ := row[constants.FieldID].(string); id != "" {
			if idx, exists := seenIDs[id]; exists {
				rows[idx] = clonePreviewLookupRow(row)
				changedRows = true
				continue
			}
			seenIDs[id] = len(rows)
		}
		rows = append(rows, clonePreviewLookupRow(row))
		appendedSynthetic++
		changedRows = true
	}
	if !changedRows {
		return result, nil
	}

	if strings.TrimSpace(lookup.SortField) != "" {
		sortPreviewLookupRows(rows, lookup.SortField, lookup.SortOrder)
	}
	if limit > 0 && len(rows) > limit {
		rows = rows[:limit]
	}

	merged := *result
	merged.Records = rows
	merged.TotalSize = result.TotalSize + appendedSynthetic
	if merged.TotalSize < len(rows) {
		merged.TotalSize = len(rows)
	}
	if merged.TotalSize == 0 {
		merged.Done = true
	}
	return &merged, nil
}

func clonePreviewLookupRow(record models.SObject) models.SObject {
	if record == nil {
		return nil
	}
	out := make(models.SObject, len(record))
	for k, v := range record {
		out[k] = v
	}
	return out
}

func sortPreviewLookupRows(rows []models.SObject, sortField string, sortOrder string) {
	desc := strings.EqualFold(strings.TrimSpace(sortOrder), "desc")
	sort.SliceStable(rows, func(i, j int) bool {
		cmp, ok := compareValues(rows[i][sortField], rows[j][sortField])
		if !ok {
			cmp = strings.Compare(fmt.Sprintf("%v", rows[i][sortField]), fmt.Sprintf("%v", rows[j][sortField]))
		}
		if desc {
			return cmp > 0
		}
		return cmp < 0
	})
}

func (e *FlowExecutor) previewSyntheticLookupRecordMatches(ctx context.Context, interview *models.FlowInterview, lookup *models.FlowRecordLookup, user *models.UserSession, record models.SObject) (bool, error) {
	if len(lookup.Filters) == 0 {
		return true, nil
	}
	results := make([]bool, len(lookup.Filters))
	for i, filter := range lookup.Filters {
		resolvedValue, _, err := e.ResolveFlowResource(ctx, interview, user, filter.Value)
		if err != nil {
			return false, err
		}
		results[i] = previewSyntheticLookupFilterMatches(ctx, e, record, filter, resolvedValue)
	}
	return evaluateFilterLogic(lookup.FilterLogic, results), nil
}

func previewSyntheticLookupFilterMatches(ctx context.Context, e *FlowExecutor, record models.SObject, filter models.FlowRecordFilter, resolvedValue interface{}) bool {
	operator, ok := previewLookupOperator(filter.Operator)
	if !ok {
		return false
	}
	fieldValue := record[filter.Field]
	filterValue, _ := extractTypedValue(resolvedValue)

	switch operator {
	case "EqualTo":
		return picklistAwareEqual(fieldValue, filterValue)
	case "NotEqualTo":
		return !picklistAwareEqual(fieldValue, filterValue)
	case "GreaterThan":
		return isGreater(fieldValue, filterValue)
	case "GreaterThanOrEqualTo":
		return isGreaterOrEqual(fieldValue, filterValue)
	case "LessThan":
		return isLess(fieldValue, filterValue)
	case "LessThanOrEqualTo":
		return isLessOrEqual(fieldValue, filterValue)
	case "Contains":
		return containsString(fieldValue, filterValue)
	case "NotContains":
		return !containsString(fieldValue, filterValue)
	case "StartsWith":
		strFilter := fmt.Sprintf("%v", filterValue)
		if e != nil {
			if matched, handled := e.sfKeyPrefixMatch(ctx, fieldValue, strFilter); handled {
				return matched
			}
		}
		return strings.HasPrefix(fmt.Sprintf("%v", fieldValue), strFilter)
	case "DoesNotStartWith":
		strFilter := fmt.Sprintf("%v", filterValue)
		if e != nil {
			if matched, handled := e.sfKeyPrefixMatch(ctx, fieldValue, strFilter); handled {
				return !matched
			}
		}
		return !strings.HasPrefix(fmt.Sprintf("%v", fieldValue), strFilter)
	case "EndsWith":
		return strings.HasSuffix(fmt.Sprintf("%v", fieldValue), fmt.Sprintf("%v", filterValue))
	case "DoesNotEndWith":
		return !strings.HasSuffix(fmt.Sprintf("%v", fieldValue), fmt.Sprintf("%v", filterValue))
	case "IsNull":
		isNull := fieldValue == nil || fieldValue == ""
		if bv, ok := filterValue.(bool); ok && !bv {
			return !isNull
		}
		return isNull
	case "IsNotNull":
		isNull := fieldValue == nil || fieldValue == ""
		if bv, ok := filterValue.(bool); ok && bv {
			return !isNull
		}
		return !isNull
	case "IsBlank":
		blank := isBlankValue(fieldValue)
		if bv, ok := filterValue.(bool); ok && !bv {
			return !blank
		}
		return blank
	case "IsNotBlank":
		return !isBlankValue(fieldValue)
	case "Between":
		return isBetween(fieldValue, filterValue)
	case "NotBetween":
		return !isBetween(fieldValue, filterValue)
	case "In":
		return isIn(fieldValue, filterValue)
	case "NotIn":
		return !isIn(fieldValue, filterValue)
	case "Includes":
		if matched, handled := e.evaluateFlowBinaryOperator(operator, fieldValue, filterValue, flowBinaryOperatorOptions{
			ctx: ctx,
		}); handled {
			return matched
		}
		return false
	case "Excludes":
		if matched, handled := e.evaluateFlowBinaryOperator(operator, fieldValue, filterValue, flowBinaryOperatorOptions{
			ctx: ctx,
		}); handled {
			return matched
		}
		return false
	case "EmptyString":
		return fmt.Sprintf("%v", fieldValue) == ""
	default:
		return false
	}
}

func previewLookupOperator(operator string) (string, bool) {
	switch strings.ToUpper(strings.TrimSpace(normalizeFlowOperator(operator))) {
	case "EQUALTO", "=":
		return "EqualTo", true
	case "NOTEQUALTO", "!=", "<>":
		return "NotEqualTo", true
	case "GREATERTHAN", ">":
		return "GreaterThan", true
	case "GREATERTHANOREQUALTO", ">=":
		return "GreaterThanOrEqualTo", true
	case "LESSTHAN", "<":
		return "LessThan", true
	case "LESSTHANOREQUALTO", "LESSOREQUALTO", "<=":
		return "LessThanOrEqualTo", true
	case "CONTAINS", "LIKE":
		return "Contains", true
	case "NOTCONTAINS":
		return "NotContains", true
	case "STARTSWITH":
		return "StartsWith", true
	case "DOESNOTSTARTWITH":
		return "DoesNotStartWith", true
	case "ENDSWITH":
		return "EndsWith", true
	case "DOESNOTENDWITH":
		return "DoesNotEndWith", true
	case "ISNULL":
		return "IsNull", true
	case "ISNOTNULL":
		return "IsNotNull", true
	case "ISBLANK":
		return "IsBlank", true
	case "ISNOTBLANK":
		return "IsNotBlank", true
	case "BETWEEN":
		return "Between", true
	case "NOTBETWEEN", "NOT BETWEEN":
		return "NotBetween", true
	case "IN":
		return "In", true
	case "NOTIN", "NOT IN":
		return "NotIn", true
	case "INCLUDES":
		return "Includes", true
	case "EXCLUDES":
		return "Excludes", true
	case "EMPTYSTRING":
		return "EmptyString", true
	default:
		return "", false
	}
}

func flowRecordLookupSyntheticOverlaySupported(lookup models.FlowRecordLookup) bool {
	for _, filter := range lookup.Filters {
		if err := validateIdentifier(filter.Field); err != nil {
			return false
		}
		if _, ok := previewLookupOperator(filter.Operator); !ok {
			return false
		}
	}
	return flowRecordLookupFilterLogicSupported(lookup.FilterLogic, len(lookup.Filters))
}

func flowRecordLookupFilterLogicSupported(logic string, filterCount int) bool {
	trimmed := strings.TrimSpace(logic)
	switch strings.ToUpper(trimmed) {
	case "", "AND", "OR":
		return true
	}
	tokens := tokenizeFilterLogic(trimmed)
	if len(tokens) == 0 {
		return true
	}
	for _, token := range tokens {
		switch token {
		case "AND", "OR", "(", ")":
			continue
		default:
			idx := 0
			for _, ch := range token {
				if ch < '0' || ch > '9' {
					return false
				}
				idx = idx*10 + int(ch-'0')
			}
			if idx < 1 || idx > filterCount {
				return false
			}
		}
	}
	condTokens, ok := tokeniseCondExpr(trimmed)
	if !ok {
		return false
	}
	for _, token := range condTokens {
		if token.kind == condTokNot {
			return false
		}
	}
	results := make([]bool, filterCount)
	_, ok = parseAndEvalConditionExpr(results, trimmed)
	return ok
}
