package analytics

import (
	"errors"
	"strings"
	"testing"

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

func TestCompileEnforcesDefaultSQLByteBound(t *testing.T) {
	atLimit := &StringLit{Value: strings.Repeat("x", maxCompiledFormulaSQLBytes-2)}
	got, err := Compile(atLimit, nil)
	if err != nil {
		t.Fatalf("Compile(at limit) error = %v", err)
	}
	if len(got) != maxCompiledFormulaSQLBytes {
		t.Fatalf("Compile(at limit) bytes = %d, want %d", len(got), maxCompiledFormulaSQLBytes)
	}

	const secret = "SQL_SECRET_MUST_NOT_LEAK"
	overLimit := &StringLit{Value: strings.Repeat("x", maxCompiledFormulaSQLBytes-len(secret)-1) + secret}
	_, err = Compile(overLimit, nil)
	if !errors.Is(err, ErrFormulaSQLOutputTooLarge) {
		t.Fatalf("Compile(over limit) error = %v, want ErrFormulaSQLOutputTooLarge", err)
	}
	var sizeErr *FormulaSQLOutputTooLargeError
	if !errors.As(err, &sizeErr) {
		t.Fatalf("Compile(over limit) error type = %T, want *FormulaSQLOutputTooLargeError", err)
	}
	if sizeErr.Scope != "formula SQL" || sizeErr.ActualBytes != maxCompiledFormulaSQLBytes+1 || sizeErr.MaxBytes != maxCompiledFormulaSQLBytes {
		t.Fatalf("Compile(over limit) size error = %+v", sizeErr)
	}
	if strings.Contains(err.Error(), secret) {
		t.Fatalf("Compile(over limit) error leaked formula SQL: %v", err)
	}
}

func TestCompileBoundPreservesUnderLimitOutput(t *testing.T) {
	ast, err := Parse(`IF(Amount__c > 0, Amount__c & " units", "none")`)
	if err != nil {
		t.Fatalf("Parse() error = %v", err)
	}
	newContext := func(limit int) *CompileContext {
		return &CompileContext{
			TableAlias:           "t",
			QualifyFields:        true,
			FieldTypes:           map[string]string{"Amount__c": string(constants.FieldTypeDouble)},
			compiledSQLByteLimit: limit,
		}
	}

	want, err := Compile(ast, newContext(maxCompiledFormulaSQLBytes))
	if err != nil {
		t.Fatalf("Compile(default bound) error = %v", err)
	}
	got, err := Compile(ast, newContext(len(want)))
	if err != nil {
		t.Fatalf("Compile(exact bound) error = %v", err)
	}
	if got != want {
		t.Fatalf("Compile(exact bound) = %q, want byte-identical %q", got, want)
	}
}

func TestCompileStopsDuplicatingEmitterAtFirstOversizedSubtree(t *testing.T) {
	var ast Node = &FieldRef{Name: "D__c"}
	for i := 0; i < 14; i++ {
		ast = &FuncCall{
			Name: "__DATE_ADD_MONTHS__",
			Args: []Node{ast, &NumberLit{Value: "1"}},
		}
	}

	const limit = 256
	const scope = "Example.Growth__c"
	_, err := Compile(ast, &CompileContext{
		TableAlias:           "t",
		QualifyFields:        true,
		FieldTypes:           map[string]string{"D__c": string(constants.FieldTypeDate)},
		compiledSQLByteLimit: limit,
		formulaSQLScope:      scope,
	})
	if !errors.Is(err, ErrFormulaSQLOutputTooLarge) {
		t.Fatalf("Compile() error = %v, want ErrFormulaSQLOutputTooLarge", err)
	}
	var sizeErr *FormulaSQLOutputTooLargeError
	if !errors.As(err, &sizeErr) {
		t.Fatalf("Compile() error type = %T, want *FormulaSQLOutputTooLargeError", err)
	}
	if sizeErr.Scope != scope || sizeErr.MaxBytes != limit {
		t.Fatalf("Compile() size error = %+v", sizeErr)
	}
	if sizeErr.ActualBytes <= limit || sizeErr.ActualBytes > 4*limit {
		t.Fatalf("Compile() rejected subtree at %d bytes, want first bounded amplification above %d", sizeErr.ActualBytes, limit)
	}
	if strings.Contains(err.Error(), "D__c") || strings.Contains(err.Error(), "DATE_ADD") {
		t.Fatalf("Compile() error leaked generated SQL: %v", err)
	}
}
