package soql

import (
	"strings"
	"testing"
)

// Regression: parser.go's semi-join detection at the IN/NOT IN branch must
// distinguish the SELECT keyword token from a string literal whose value
// happens to be "SELECT" (or "select", "INSERT", any keyword-shaped value).
//
// The lexer strips quotes from string literals (lexer.go:153 sets
// TokenString.Literal to the unquoted content), so checking only
// `p.curToken().Literal == "SELECT"` would match a quoted 'SELECT' literal
// and re-route into parseSemiJoinSubquery — breaking valid SOQL like
// `WHERE Name IN ('SELECT', 'Other')`. The real check must also verify
// `Type == TokenKeyword` (which is only set when the unquoted identifier
// matches isKeyword(lit)).
//
// This was Codex Round 5 finding on PR #543. It was a real bug — settled
// empirically by running the test below before vs after the fix.
func TestParser_LiteralStringSelectInINList_NotMisclassifiedAsSemiJoin(t *testing.T) {
	cases := []string{
		`SELECT Id FROM Account WHERE Name IN ('SELECT', 'Other')`,
		`SELECT Id FROM Account WHERE Name IN ('select')`,
		`SELECT Id FROM Account WHERE Name IN ('SELECT')`,
		`SELECT Id FROM Account WHERE Name = 'SELECT'`,
		`SELECT Id FROM Account WHERE Status NOT IN ('SELECT', 'Active')`,
		`SELECT Id FROM Account WHERE Name IN ('FROM', 'WHERE', 'GROUP', 'HAVING')`,
		`SELECT Id FROM Account WHERE Type IN ('select-type', 'Other')`,
	}
	for _, q := range cases {
		t.Run(q, func(t *testing.T) {
			req, err := ParseSOQL(q)
			if err != nil {
				t.Fatalf("ParseSOQL(%q) UNEXPECTED error: %v", q, err)
			}
			if len(req.SemiJoins) != 0 {
				t.Fatalf("literal IN with keyword-shaped value misregistered a semi-join: %+v", req.SemiJoins)
			}
		})
	}
}

// Adversarial: real semi-join + literal IN with quoted 'SELECT' must
// continue to be distinguished correctly. Both forms parse, neither
// leaks into the other.
func TestParser_StringLiteralSelectVsRealSemiJoin(t *testing.T) {
	literalQ := `SELECT Id FROM Account WHERE Name IN ('SELECT', 'INSERT')`
	semiJoinQ := `SELECT Id FROM Quote WHERE OpportunityId IN (SELECT Id FROM Opportunity WHERE Name = 'X')`

	req1, err1 := ParseSOQL(literalQ)
	if err1 != nil {
		t.Fatalf("literal IN parse failed: %v", err1)
	}
	if len(req1.SemiJoins) != 0 {
		t.Fatalf("literal IN must NOT register a semi-join: %+v", req1.SemiJoins)
	}
	expr := predicateExpression(t, req1.Filter)
	if !strings.Contains(expr, "'SELECT'") || !strings.Contains(expr, "'INSERT'") {
		t.Fatalf("literal IN values lost in predicate expression: %q", expr)
	}

	req2, err2 := ParseSOQLWith(semiJoinQ, ParseOpts{AllowSemiJoin: true})
	if err2 != nil {
		t.Fatalf("real semi-join parse failed: %v", err2)
	}
	if len(req2.SemiJoins) != 1 {
		t.Fatalf("real semi-join must register: %+v", req2.SemiJoins)
	}
}

// Strict-default path: literal IN with 'SELECT' must NOT become a
// semi-join validation error from ParseSOQL's strict gate either.
// (The strict gate only fires if SemiJoins != nil; the disambiguation
// fix prevents this from happening.)
func TestParser_StrictDefaultAcceptsLiteralINWithSelectValue(t *testing.T) {
	q := `SELECT Id FROM Account WHERE Name IN ('SELECT')`
	req, err := ParseSOQL(q)
	if err != nil {
		t.Fatalf("ParseSOQL strict-default rejected valid literal IN with 'SELECT' value: %v", err)
	}
	if len(req.SemiJoins) != 0 {
		t.Fatalf("literal IN must not register semi-join under strict default: %+v", req.SemiJoins)
	}
}
