package config

import (
	"fmt"
	"sort"
	"strings"
	"unicode"
)

const maxDSLExpansions = 1000

// Token types for the DSL lexer
type tokenType int

const (
	tokIdent  tokenType = iota // model alias or name
	tokAnd                     // &
	tokOr                      // |
	tokLParen                  // (
	tokRParen                  // )
	tokRef                     // +setName
	tokEOF
)

type token struct {
	typ tokenType
	val string
}

// tokenize splits a DSL string into tokens.
func tokenize(input string) ([]token, error) {
	var tokens []token
	i := 0
	runes := []rune(input)

	for i < len(runes) {
		ch := runes[i]

		// skip whitespace
		if unicode.IsSpace(ch) {
			i++
			continue
		}

		switch ch {
		case '&':
			tokens = append(tokens, token{tokAnd, "&"})
			i++
		case '|':
			tokens = append(tokens, token{tokOr, "|"})
			i++
		case '(':
			tokens = append(tokens, token{tokLParen, "("})
			i++
		case ')':
			tokens = append(tokens, token{tokRParen, ")"})
			i++
		case '+':
			// +ref: read the identifier that follows
			i++
			start := i
			for i < len(runes) && isIdentChar(runes[i]) {
				i++
			}
			if i == start {
				return nil, fmt.Errorf("expected set name after '+' at position %d", start)
			}
			tokens = append(tokens, token{tokRef, string(runes[start:i])})
		default:
			if isIdentChar(ch) {
				start := i
				for i < len(runes) && isIdentChar(runes[i]) {
					i++
				}
				tokens = append(tokens, token{tokIdent, string(runes[start:i])})
			} else {
				return nil, fmt.Errorf("unexpected character %q at position %d", ch, i)
			}
		}
	}

	tokens = append(tokens, token{tokEOF, ""})
	return tokens, nil
}

func isIdentChar(ch rune) bool {
	return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == '-' || ch == '.'
}

// AST node types
type dslNode interface {
	dslNode()
}

type andNode struct {
	children []dslNode
}

type orNode struct {
	children []dslNode
}

type leafNode struct {
	name string
}

type refNode struct {
	setName string
}

func (andNode) dslNode()  {}
func (orNode) dslNode()   {}
func (leafNode) dslNode() {}
func (refNode) dslNode()  {}

// parser holds state for recursive-descent parsing.
type parser struct {
	tokens []token
	pos    int
}

func (p *parser) peek() token {
	if p.pos < len(p.tokens) {
		return p.tokens[p.pos]
	}
	return token{tokEOF, ""}
}

func (p *parser) next() token {
	t := p.peek()
	if t.typ != tokEOF {
		p.pos++
	}
	return t
}

func (p *parser) expect(typ tokenType) (token, error) {
	t := p.next()
	if t.typ != typ {
		return t, fmt.Errorf("expected token type %d, got %q", typ, t.val)
	}
	return t, nil
}

// Grammar:
//
//	expr    = andExpr
//	andExpr = orExpr ('&' orExpr)*
//	orExpr  = atom ('|' atom)*
//	atom    = ident | '+' ident | '(' expr ')'
//
// & binds tighter than |, so "a | b & c" means "a | (b & c)"
func parse(tokens []token) (dslNode, error) {
	p := &parser{tokens: tokens}
	node, err := p.parseExpr()
	if err != nil {
		return nil, err
	}
	if p.peek().typ != tokEOF {
		return nil, fmt.Errorf("unexpected token %q after expression", p.peek().val)
	}
	return node, nil
}

func (p *parser) parseExpr() (dslNode, error) {
	return p.parseOrExpr()
}

func (p *parser) parseOrExpr() (dslNode, error) {
	left, err := p.parseAndExpr()
	if err != nil {
		return nil, err
	}

	if p.peek().typ == tokOr {
		children := []dslNode{left}
		for p.peek().typ == tokOr {
			p.next() // consume |
			right, err := p.parseAndExpr()
			if err != nil {
				return nil, err
			}
			children = append(children, right)
		}
		return orNode{children: children}, nil
	}

	return left, nil
}

func (p *parser) parseAndExpr() (dslNode, error) {
	left, err := p.parseAtom()
	if err != nil {
		return nil, err
	}

	if p.peek().typ == tokAnd {
		children := []dslNode{left}
		for p.peek().typ == tokAnd {
			p.next() // consume &
			right, err := p.parseAtom()
			if err != nil {
				return nil, err
			}
			children = append(children, right)
		}
		return andNode{children: children}, nil
	}

	return left, nil
}

func (p *parser) parseAtom() (dslNode, error) {
	t := p.peek()

	switch t.typ {
	case tokIdent:
		p.next()
		return leafNode{name: t.val}, nil

	case tokRef:
		p.next()
		return refNode{setName: t.val}, nil

	case tokLParen:
		p.next() // consume (
		node, err := p.parseExpr()
		if err != nil {
			return nil, err
		}
		if _, err := p.expect(tokRParen); err != nil {
			return nil, fmt.Errorf("missing closing parenthesis")
		}
		return node, nil

	default:
		return nil, fmt.Errorf("unexpected token %q", t.val)
	}
}

// expand walks the AST and produces all combinations.
// resolvedRefs contains previously expanded sets for +ref resolution.
func expand(node dslNode, resolvedRefs map[string][][]string) ([][]string, error) {
	switch n := node.(type) {
	case leafNode:
		return [][]string{{n.name}}, nil

	case refNode:
		expanded, ok := resolvedRefs[n.setName]
		if !ok {
			return nil, fmt.Errorf("unknown set reference +%s", n.setName)
		}
		// Return a copy
		result := make([][]string, len(expanded))
		for i, combo := range expanded {
			result[i] = make([]string, len(combo))
			copy(result[i], combo)
		}
		return result, nil

	case orNode:
		// Union of all children's expansions
		var result [][]string
		for _, child := range n.children {
			childResult, err := expand(child, resolvedRefs)
			if err != nil {
				return nil, err
			}
			result = append(result, childResult...)
			if len(result) > maxDSLExpansions {
				return nil, fmt.Errorf("DSL expansion exceeded %d combinations", maxDSLExpansions)
			}
		}
		return result, nil

	case andNode:
		// Cartesian product across children
		result := [][]string{{}} // start with one empty combo
		for _, child := range n.children {
			childResult, err := expand(child, resolvedRefs)
			if err != nil {
				return nil, err
			}
			result, err = cartesianProduct(result, childResult, maxDSLExpansions)
			if err != nil {
				return nil, err
			}
		}
		return result, nil

	default:
		return nil, fmt.Errorf("unknown node type %T", node)
	}
}

// cartesianProduct computes the cartesian product of two sets of combinations.
// It returns an error if the product would exceed cap.
func cartesianProduct(left, right [][]string, cap int) ([][]string, error) {
	if int64(len(left))*int64(len(right)) > int64(cap) {
		return nil, fmt.Errorf("DSL expansion exceeded %d combinations", cap)
	}
	result := make([][]string, 0, len(left)*len(right))
	for _, l := range left {
		for _, r := range right {
			combo := make([]string, 0, len(l)+len(r))
			combo = append(combo, l...)
			combo = append(combo, r...)
			result = append(result, combo)
		}
	}
	return result, nil
}

// ParseAndExpandDSL tokenizes, parses, and expands a DSL string.
// resolvedRefs contains previously expanded sets for +ref inlining.
func ParseAndExpandDSL(dsl string, resolvedRefs map[string][][]string) ([][]string, error) {
	dsl = strings.TrimSpace(dsl)
	if dsl == "" {
		return nil, fmt.Errorf("empty DSL expression")
	}

	tokens, err := tokenize(dsl)
	if err != nil {
		return nil, fmt.Errorf("tokenize: %w", err)
	}

	tree, err := parse(tokens)
	if err != nil {
		return nil, fmt.Errorf("parse: %w", err)
	}

	result, err := expand(tree, resolvedRefs)
	if err != nil {
		return nil, err
	}

	// Deduplicate models within each combination and sort for consistency
	for i, combo := range result {
		result[i] = dedupAndSort(combo)
	}

	return result, nil
}

// dedupAndSort removes duplicate entries and sorts alphabetically.
func dedupAndSort(items []string) []string {
	seen := make(map[string]bool, len(items))
	var unique []string
	for _, item := range items {
		if !seen[item] {
			seen[item] = true
			unique = append(unique, item)
		}
	}
	sort.Strings(unique)
	return unique
}

// extractRefs scans a DSL string for +ref tokens without full parsing.
// Used for building the dependency graph for topological sorting.
func extractRefs(dsl string) ([]string, error) {
	tokens, err := tokenize(dsl)
	if err != nil {
		return nil, err
	}

	var refs []string
	seen := make(map[string]bool)
	for _, t := range tokens {
		if t.typ == tokRef && !seen[t.val] {
			seen[t.val] = true
			refs = append(refs, t.val)
		}
	}
	return refs, nil
}
