package models_test

import (
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

// QuerySpecFromQueryRequest is the single SOQL→spec mapper shared by every read
// door (dialect-sf-crm query/queryMore/semi-join and the build-tagged rest /query
// + /tooling/query handlers). The multi-column ORDER BY must survive in full — a
// regression here silently dropped every sort term after the first on whichever
// door called it.
func TestQuerySpecFromQueryRequest_MapsMultiColumnOrderBy(t *testing.T) {
	req := &models.QueryRequest{
		ObjectAPIName: "Account",
		SelectFields:  []string{"Id", "Name"},
		// The SOQL parser keeps SortField = first column for MRU/view routing while
		// recording the full ordered list on OrderBy.
		SortField:     "Industry",
		SortDirection: "ASC",
		OrderBy: []models.SortCriterion{
			{Field: "Industry", Direction: "ASC"},
			{Field: "AnnualRevenue", Direction: "DESC"},
			{Field: "Id", Direction: "ASC"},
		},
	}

	spec := models.QuerySpecFromQueryRequest(req)

	want := []struct{ field, dir string }{
		{"Industry", "ASC"},
		{"AnnualRevenue", "DESC"},
		{"Id", "ASC"},
	}
	if len(spec.OrderBy) != len(want) {
		t.Fatalf("expected %d ORDER BY terms, got %d (%+v)", len(want), len(spec.OrderBy), spec.OrderBy)
	}
	for i, w := range want {
		if spec.OrderBy[i].Field != w.field || spec.OrderBy[i].Direction != w.dir {
			t.Errorf("term %d = {%s %s}, want {%s %s}",
				i, spec.OrderBy[i].Field, spec.OrderBy[i].Direction, w.field, w.dir)
		}
	}
	// SortField/SortDirection preserved verbatim (MRU routing / read-target view selection).
	if spec.SortField != "Industry" || spec.SortDirection != "ASC" {
		t.Errorf("first-column SortField/SortDirection not preserved: %q/%q", spec.SortField, spec.SortDirection)
	}
}

func TestQuerySpecFromQueryRequest_NilSafeAndNoOrderBy(t *testing.T) {
	if spec := models.QuerySpecFromQueryRequest(nil); len(spec.OrderBy) != 0 || spec.ObjectAPIName != "" {
		t.Errorf("nil request should yield zero spec, got %+v", spec)
	}

	spec := models.QuerySpecFromQueryRequest(&models.QueryRequest{ObjectAPIName: "Account"})
	if len(spec.OrderBy) != 0 || spec.SortField != "" {
		t.Errorf("no ORDER BY should leave OrderBy/SortField empty, got %+v", spec)
	}
}

func TestQuerySpecFromQueryRequest_LegacyStringWinsWhenDualPopulated(t *testing.T) {
	req := &models.QueryRequest{
		ObjectAPIName:   "Account",
		FilterExpr:      "Name == 'legacy'",
		Filter:          queryspec.FieldPredicate("Name", "=", "Acme"),
		Having:          "COUNT(Id) > 1",
		HavingPredicate: queryspec.ComparisonPredicate(queryspec.FunctionOperand("COUNT", queryspec.FieldOperand("Id")), ">", queryspec.RawOperand("1")),
		SubQueries:      []models.SubQueryRequest{{RelationshipName: "Contacts", FilterExpr: "LastName == 'legacy'", Filter: queryspec.FieldPredicate("LastName", "=", "Smith")}},
	}

	spec := models.QuerySpecFromQueryRequest(req)

	if got := predicateExpression(t, spec.Filter); got != "Name == 'legacy'" {
		t.Fatalf("Filter expression = %q, want legacy string while dual-populated", got)
	}
	if got := predicateExpression(t, spec.Having); got != "COUNT(Id) > 1" {
		t.Fatalf("Having expression = %q, want legacy string while dual-populated", got)
	}
	if len(spec.SubQueries) != 1 {
		t.Fatalf("SubQueries = %#v", spec.SubQueries)
	}
	if got := predicateExpression(t, spec.SubQueries[0].Filter); got != "LastName == 'legacy'" {
		t.Fatalf("SubQuery filter expression = %q, want legacy string while dual-populated", got)
	}
}

func TestQuerySpecFromQueryRequest_UsesStructuredPredicatesWhenLegacyStringsAbsent(t *testing.T) {
	req := &models.QueryRequest{
		ObjectAPIName:   "Account",
		Filter:          queryspec.FieldPredicate("Name", "=", "Acme"),
		HavingPredicate: queryspec.ComparisonPredicate(queryspec.FunctionOperand("COUNT", queryspec.FieldOperand("Id")), ">", queryspec.RawOperand("1")),
		SubQueries:      []models.SubQueryRequest{{RelationshipName: "Contacts", Filter: queryspec.FieldPredicate("LastName", "=", "Smith")}},
	}

	spec := models.QuerySpecFromQueryRequest(req)

	if got := predicateExpression(t, spec.Filter); got != "Name = 'Acme'" {
		t.Fatalf("Filter expression = %q, want structured predicate projection", got)
	}
	if got := predicateExpression(t, spec.Having); got != "COUNT(Id) > 1" {
		t.Fatalf("Having expression = %q, want structured predicate projection", got)
	}
	if len(spec.SubQueries) != 1 {
		t.Fatalf("SubQueries = %#v", spec.SubQueries)
	}
	if got := predicateExpression(t, spec.SubQueries[0].Filter); got != "LastName = 'Smith'" {
		t.Fatalf("SubQuery filter expression = %q, want structured predicate projection", got)
	}
}

func TestQuerySpecFromQueryRequest_FallsBackToLegacyStringPredicates(t *testing.T) {
	spec := models.QuerySpecFromQueryRequest(&models.QueryRequest{
		ObjectAPIName: "Account",
		FilterExpr:    "Name == 'legacy'",
		Having:        "COUNT(Id) > 1",
		SubQueries:    []models.SubQueryRequest{{RelationshipName: "Contacts", FilterExpr: "LastName == 'legacy'"}},
	})

	if got := predicateExpression(t, spec.Filter); got != "Name == 'legacy'" {
		t.Fatalf("Filter expression = %q, want legacy formula fallback", got)
	}
	if got := predicateExpression(t, spec.Having); got != "COUNT(Id) > 1" {
		t.Fatalf("Having expression = %q, want legacy formula fallback", got)
	}
	if len(spec.SubQueries) != 1 || predicateExpression(t, spec.SubQueries[0].Filter) != "LastName == 'legacy'" {
		t.Fatalf("SubQuery filter = %#v, want legacy formula fallback", spec.SubQueries)
	}
}

// Child-relationship subquery ORDER BY is mapped independently and was always
// forwarded; guard it so the shared extraction did not drop it.
func TestQuerySpecFromQueryRequest_MapsSubQueryOrderBy(t *testing.T) {
	req := &models.QueryRequest{
		ObjectAPIName: "Account",
		SubQueries: []models.SubQueryRequest{{
			RelationshipName: "Contacts",
			SelectFields:     []string{"Id"},
			OrderBy:          []models.SortCriterion{{Field: "LastName", Direction: "ASC"}},
		}},
	}

	spec := models.QuerySpecFromQueryRequest(req)

	if len(spec.SubQueries) != 1 || len(spec.SubQueries[0].OrderBy) != 1 ||
		spec.SubQueries[0].OrderBy[0].Field != "LastName" {
		t.Fatalf("subquery OrderBy not mapped: %+v", spec.SubQueries)
	}
}

func predicateExpression(t *testing.T, predicate queryspec.Predicate) string {
	t.Helper()
	lowering, err := queryspec.LowerPredicate(predicate, queryspec.PredicateLoweringExpressionOnly)
	if err != nil {
		t.Fatalf("LowerPredicate(expression only) error = %v", err)
	}
	return lowering.Expression
}
