package models

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

var flowAtomicParentPrelockIdentifier = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)

var flowAtomicParentPrelockOperators = map[string]struct{}{
	"=":                    {},
	"!=":                   {},
	"<>":                   {},
	">":                    {},
	">=":                   {},
	"<":                    {},
	"<=":                   {},
	"BETWEEN":              {},
	"CONTAINS":             {},
	"DOESNOTENDWITH":       {},
	"DOESNOTSTARTWITH":     {},
	"EMPTYSTRING":          {},
	"ENDSWITH":             {},
	"EQUALTO":              {},
	"EXCLUDES":             {},
	"GREATERTHAN":          {},
	"GREATERTHANOREQUALTO": {},
	"INCLUDES":             {},
	"ISBLANK":              {},
	"ISNOTBLANK":           {},
	"ISNOTNULL":            {},
	"ISNULL":               {},
	"LESSOREQUALTO":        {},
	"LESSTHAN":             {},
	"LESSTHANOREQUALTO":    {},
	"LIKE":                 {},
	"NOTBETWEEN":           {},
	"NOTCONTAINS":          {},
	"NOTEQUALTO":           {},
	"STARTSWITH":           {},
}

// ValidateFlowAtomicParentPrelocks enforces the input-bound discovery contract
// shared by authoring and runtime. Prelock discovery happens before the first
// Flow element, so only declared input variables and record inputs can be used.
func ValidateFlowAtomicParentPrelocks(meta FlowMetadata) error {
	if len(meta.AtomicParentPrelocks) == 0 {
		return nil
	}
	if !meta.RunAtomically {
		return fmt.Errorf("atomicParentPrelocks requires runAtomically=true")
	}

	initialRoots := make(map[string]struct{})
	if meta.Start != nil && strings.TrimSpace(meta.Start.Object) != "" {
		initialRoots["$Record"] = struct{}{}
	}
	for _, variable := range meta.Variables {
		if variable.IsInput {
			if name := strings.TrimSpace(variable.Name); name != "" {
				initialRoots[name] = struct{}{}
			}
		}
	}

	seen := make(map[string]struct{}, len(meta.AtomicParentPrelocks))
	for i, prelock := range meta.AtomicParentPrelocks {
		name := strings.TrimSpace(prelock.Name)
		if name == "" {
			return fmt.Errorf("atomicParentPrelocks[%d] requires a name", i)
		}
		if _, exists := seen[name]; exists {
			return fmt.Errorf("atomicParentPrelocks contains duplicate name %q", name)
		}
		seen[name] = struct{}{}

		if !flowAtomicParentPrelockIdentifier.MatchString(strings.TrimSpace(prelock.ParentObject)) {
			return fmt.Errorf("atomic parent prelock %q requires a valid parentObject", name)
		}
		hasSource := strings.TrimSpace(prelock.SourceObject) != ""
		if len(prelock.RecordIDs) == 0 && !hasSource {
			return fmt.Errorf("atomic parent prelock %q requires recordIds or a source query", name)
		}
		for j, value := range prelock.RecordIDs {
			if err := validateFlowAtomicParentPrelockValue(value, initialRoots); err != nil {
				return fmt.Errorf("atomic parent prelock %q recordIds[%d]: %w", name, j, err)
			}
		}

		if !hasSource {
			if strings.TrimSpace(prelock.ParentReferenceField) != "" || len(prelock.Filters) > 0 || strings.TrimSpace(prelock.FilterLogic) != "" {
				return fmt.Errorf("atomic parent prelock %q declares source fields without sourceObject", name)
			}
			continue
		}
		if !flowAtomicParentPrelockIdentifier.MatchString(strings.TrimSpace(prelock.SourceObject)) ||
			!flowAtomicParentPrelockIdentifier.MatchString(strings.TrimSpace(prelock.ParentReferenceField)) {
			return fmt.Errorf("atomic parent prelock %q requires valid sourceObject and parentReferenceField", name)
		}
		if len(prelock.Filters) == 0 {
			return fmt.Errorf("atomic parent prelock %q source query requires at least one filter", name)
		}
		if err := ValidateFlowFilterLogic(prelock.FilterLogic, len(prelock.Filters)); err != nil {
			return fmt.Errorf("atomic parent prelock %q has invalid filterLogic: %w", name, err)
		}
		for j, filter := range prelock.Filters {
			if !flowAtomicParentPrelockIdentifier.MatchString(strings.TrimSpace(filter.Field)) {
				return fmt.Errorf("atomic parent prelock %q filter %d requires a valid field", name, j+1)
			}
			operator := strings.ToUpper(strings.TrimSpace(filter.Operator))
			if _, ok := flowAtomicParentPrelockOperators[operator]; !ok {
				return fmt.Errorf("atomic parent prelock %q filter %d has unsupported operator %q", name, j+1, filter.Operator)
			}
			if err := validateFlowAtomicParentPrelockValue(filter.Value, initialRoots); err != nil {
				return fmt.Errorf("atomic parent prelock %q filter %d: %w", name, j+1, err)
			}
		}
	}
	return nil
}

type flowAtomicParentPrelockLogicToken struct {
	kind  byte
	index int
}

const (
	flowAtomicParentPrelockLogicIndex byte = iota
	flowAtomicParentPrelockLogicAnd
	flowAtomicParentPrelockLogicOr
	flowAtomicParentPrelockLogicLeft
	flowAtomicParentPrelockLogicRight
)

// ValidateFlowFilterLogic accepts the default AND/OR forms or a
// complete indexed AND/OR expression. Every authored filter must be referenced
// and every token must be consumed, preventing selector/evaluator drift.
func ValidateFlowFilterLogic(logic string, filterCount int) error {
	trimmed := strings.TrimSpace(logic)
	switch strings.ToUpper(trimmed) {
	case "", "AND", "OR":
		return nil
	}

	tokens, err := tokenizeFlowAtomicParentPrelockFilterLogic(trimmed, filterCount)
	if err != nil {
		return err
	}
	parser := flowAtomicParentPrelockLogicParser{tokens: tokens, referenced: make([]bool, filterCount)}
	if err := parser.parseOr(); err != nil {
		return err
	}
	if parser.pos != len(tokens) {
		return fmt.Errorf("unexpected token at position %d", parser.pos+1)
	}
	for i, referenced := range parser.referenced {
		if !referenced {
			return fmt.Errorf("filter reference %d is not used", i+1)
		}
	}
	return nil
}

func tokenizeFlowAtomicParentPrelockFilterLogic(logic string, filterCount int) ([]flowAtomicParentPrelockLogicToken, error) {
	var tokens []flowAtomicParentPrelockLogicToken
	for i := 0; i < len(logic); {
		switch logic[i] {
		case ' ', '\t', '\n', '\r':
			i++
			continue
		case '(':
			tokens = append(tokens, flowAtomicParentPrelockLogicToken{kind: flowAtomicParentPrelockLogicLeft})
			i++
			continue
		case ')':
			tokens = append(tokens, flowAtomicParentPrelockLogicToken{kind: flowAtomicParentPrelockLogicRight})
			i++
			continue
		}

		start := i
		for i < len(logic) && ((logic[i] >= 'A' && logic[i] <= 'Z') || (logic[i] >= 'a' && logic[i] <= 'z') || (logic[i] >= '0' && logic[i] <= '9')) {
			i++
		}
		if start == i {
			return nil, fmt.Errorf("unsupported character %q", logic[i])
		}
		word := logic[start:i]
		switch strings.ToUpper(word) {
		case "AND":
			tokens = append(tokens, flowAtomicParentPrelockLogicToken{kind: flowAtomicParentPrelockLogicAnd})
		case "OR":
			tokens = append(tokens, flowAtomicParentPrelockLogicToken{kind: flowAtomicParentPrelockLogicOr})
		default:
			index, parseErr := strconv.Atoi(word)
			if parseErr != nil || index < 1 || index > filterCount {
				return nil, fmt.Errorf("filter reference %q is invalid or out of range", word)
			}
			tokens = append(tokens, flowAtomicParentPrelockLogicToken{kind: flowAtomicParentPrelockLogicIndex, index: index})
		}
	}
	if len(tokens) == 0 {
		return nil, fmt.Errorf("custom filterLogic is empty")
	}
	return tokens, nil
}

type flowAtomicParentPrelockLogicParser struct {
	tokens     []flowAtomicParentPrelockLogicToken
	pos        int
	referenced []bool
}

func (p *flowAtomicParentPrelockLogicParser) parseOr() error {
	if err := p.parseAnd(); err != nil {
		return err
	}
	for p.peek(flowAtomicParentPrelockLogicOr) {
		p.pos++
		if err := p.parseAnd(); err != nil {
			return err
		}
	}
	return nil
}

func (p *flowAtomicParentPrelockLogicParser) parseAnd() error {
	if err := p.parsePrimary(); err != nil {
		return err
	}
	for p.peek(flowAtomicParentPrelockLogicAnd) {
		p.pos++
		if err := p.parsePrimary(); err != nil {
			return err
		}
	}
	return nil
}

func (p *flowAtomicParentPrelockLogicParser) parsePrimary() error {
	if p.pos >= len(p.tokens) {
		return fmt.Errorf("filterLogic ends before an operand")
	}
	token := p.tokens[p.pos]
	switch token.kind {
	case flowAtomicParentPrelockLogicIndex:
		p.referenced[token.index-1] = true
		p.pos++
		return nil
	case flowAtomicParentPrelockLogicLeft:
		p.pos++
		if err := p.parseOr(); err != nil {
			return err
		}
		if !p.peek(flowAtomicParentPrelockLogicRight) {
			return fmt.Errorf("filterLogic has an unmatched opening parenthesis")
		}
		p.pos++
		return nil
	default:
		return fmt.Errorf("expected a filter reference or opening parenthesis at position %d", p.pos+1)
	}
}

func (p *flowAtomicParentPrelockLogicParser) peek(kind byte) bool {
	return p.pos < len(p.tokens) && p.tokens[p.pos].kind == kind
}

func validateFlowAtomicParentPrelockValue(value any, initialRoots map[string]struct{}) error {
	switch typed := value.(type) {
	case string:
		return fmt.Errorf("bare string values are ambiguous; use stringValue for a literal or elementReference for an input reference")
	case []string:
		if len(typed) > 0 {
			return fmt.Errorf("bare string values are ambiguous; use typed value wrappers")
		}
	case []interface{}:
		for _, item := range typed {
			if err := validateFlowAtomicParentPrelockValue(item, initialRoots); err != nil {
				return err
			}
		}
	case map[string]interface{}:
		if len(typed) != 1 {
			return fmt.Errorf("value wrapper must contain exactly one supported typed value or elementReference")
		}
		raw, isReference := typed["elementReference"]
		if !isReference {
			for _, key := range []string{"stringValue", "numberValue", "booleanValue", "dateValue", "dateTimeValue"} {
				if value, ok := typed[key]; ok {
					return validateFlowAtomicParentPrelockLiteral(key, value)
				}
			}
			return fmt.Errorf("map value must be a supported typed literal wrapper or elementReference")
		}
		ref, ok := raw.(string)
		if !ok || strings.TrimSpace(ref) == "" {
			return fmt.Errorf("elementReference must be a non-empty string")
		}
		ref = strings.TrimSpace(ref)
		if strings.HasPrefix(ref, "{!") && strings.HasSuffix(ref, "}") {
			ref = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(ref, "{!"), "}"))
		}
		root := strings.Split(ref, ".")[0]
		if _, ok := initialRoots[root]; !ok {
			return fmt.Errorf("elementReference %q is not available before the first Flow element; use an input variable or $Record input", ref)
		}
	}
	return nil
}

func validateFlowAtomicParentPrelockLiteral(key string, value any) error {
	valid := false
	switch key {
	case "stringValue", "dateValue", "dateTimeValue":
		_, valid = value.(string)
	case "booleanValue":
		_, valid = value.(bool)
	case "numberValue":
		switch value.(type) {
		case int, int8, int16, int32, int64,
			uint, uint8, uint16, uint32, uint64,
			float32, float64, json.Number:
			valid = true
		}
	}
	if !valid {
		return fmt.Errorf("%s must contain a scalar value of the declared type", key)
	}
	return nil
}
