package fn_operations

import (
	"fmt"
	"regexp"
	"strings"
	"time"
)

// fieldPattern matches bare field names (word chars) that are NOT already inside
// a json_extract call, a SQL keyword, a string literal, or a number.
var fieldPattern = regexp.MustCompile(`\b([a-zA-Z_][a-zA-Z0-9_]*)\b`)

// sqlKeywords that should not be rewritten to json_extract.
var sqlKeywords = map[string]bool{
	"AND": true, "OR": true, "NOT": true, "IS": true, "NULL": true,
	"IN": true, "BETWEEN": true, "LIKE": true, "GLOB": true,
	"TRUE": true, "FALSE": true, "CASE": true, "WHEN": true,
	"THEN": true, "ELSE": true, "END": true, "SELECT": true,
	"FROM": true, "WHERE": true, "AS": true, "CAST": true,
}

// sqlFunctions that should not be rewritten.
var sqlFunctions = map[string]bool{
	"json_extract": true, "datetime": true, "now": true,
	"abs": true, "avg": true, "count": true, "max": true, "min": true,
	"sum": true, "total": true, "length": true, "typeof": true,
	"coalesce": true, "ifnull": true, "nullif": true,
	"upper": true, "lower": true, "trim": true, "replace": true,
	"substr": true, "instr": true, "round": true,
}

// rewriteRule transforms a rule expression into SQL that operates on entity metadata.
// Bare field names are rewritten to json_extract(metadata, '$.field').
// If the rule already uses json_extract, it is left as-is.
func rewriteRule(rule string) string {
	if strings.Contains(rule, "json_extract") {
		return rule
	}

	return fieldPattern.ReplaceAllStringFunc(rule, func(match string) string {
		upper := strings.ToUpper(match)
		if sqlKeywords[upper] {
			return match
		}
		if sqlFunctions[strings.ToLower(match)] {
			return match
		}
		// Skip numeric-looking tokens (shouldn't match the regex, but be safe)
		if match[0] >= '0' && match[0] <= '9' {
			return match
		}
		return fmt.Sprintf("json_extract(metadata, '$.%s')", match)
	})
}

// EvalAssertion evaluates a single assertion against its entity's metadata.
// Returns a result with pass/fail/skip status.
func EvalAssertion(db *DB, a *Assertion, executionID string) (*AssertionResult, error) {
	result := &AssertionResult{
		ID:          fmt.Sprintf("ar_%s_%d", a.ID, time.Now().UnixNano()),
		AssertionID: a.ID,
		ExecutionID: executionID,
		EvaluatedAt: time.Now().UTC(),
	}

	rewritten := rewriteRule(a.Rule)

	q := fmt.Sprintf(`
		SELECT CASE WHEN (%s) THEN 'pass' ELSE 'fail' END,
		       metadata
		FROM entities WHERE id = ?`, rewritten)

	var status, metadataJSON string
	err := db.conn.QueryRow(q, a.EntityID).Scan(&status, &metadataJSON)
	if err != nil {
		result.Status = ResultSkip
		result.Message = fmt.Sprintf("evaluation error: %v", err)
		return result, nil
	}

	if status == "pass" {
		result.Status = ResultPass
	} else {
		result.Status = ResultFail
		result.Value = unmarshalJSON(metadataJSON)
		result.Message = fmt.Sprintf("rule failed: %s", a.Rule)
	}

	return result, nil
}

// EvalEntityAssertions evaluates all active assertions for an entity.
// Returns results and persists them in the database.
func EvalEntityAssertions(db *DB, entityID, executionID string) ([]AssertionResult, error) {
	active := true
	assertions, err := db.ListAssertions(entityID, &active)
	if err != nil {
		return nil, fmt.Errorf("listing assertions for entity %s: %w", entityID, err)
	}

	var results []AssertionResult
	for _, a := range assertions {
		ar, err := EvalAssertion(db, &a, executionID)
		if err != nil {
			return nil, fmt.Errorf("evaluating assertion %s: %w", a.ID, err)
		}

		if err := db.InsertAssertionResult(ar); err != nil {
			return nil, fmt.Errorf("storing result for assertion %s: %w", a.ID, err)
		}

		results = append(results, *ar)
	}

	return results, nil
}
