package dialectsfcrm

import (
	"reflect"
	"testing"

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

func TestReportDefinitionModelBoundaryDoesNotExposeReportTypeAlias(t *testing.T) {
	if _, ok := reflect.TypeOf(models.ReportDefinition{}).FieldByName("ReportType"); ok {
		t.Fatalf("ReportDefinition still exposes legacy ReportType alias; Salesforce reportType must map to SourceRef at the dialect boundary")
	}
}

func TestReportDefinitionDialectParsesSalesforceImportedShape(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	def, err := dialect.ParseStoredDefinition(contract.Record{
		"Id":           "00O1",
		"Name":         "Pipeline",
		"SourceObject": "OpportunityWithProducts",
		"ReportType":   "summary",
		"Filters":      `[{"column":"StageName","operator":"equals","value":"Prospecting"}]`,
		"Grouping":     `[{"name":"STAGE_NAME","dateGranularity":"None"}]`,
		"Aggregates":   `["RowCount","s!AMOUNT","a!AGE","mx!CLOSE_DATE","mn!CREATED_DATE"]`,
		"StandardDateFilter": `{
			"column": "CloseDate",
			"durationValue": "CUSTOM",
			"startDate": "2026-01-01",
			"endDate": "2026-01-31"
		}`,
	})
	if err != nil {
		t.Fatalf("ParseStoredDefinition: %v", err)
	}
	if def.DisplayMode != "SUMMARY" {
		t.Fatalf("DisplayMode = %q, want SUMMARY", def.DisplayMode)
	}
	if len(def.Grouping) != 1 || def.Grouping[0] != "STAGE_NAME" {
		t.Fatalf("Grouping = %+v, want STAGE_NAME", def.Grouping)
	}
	if len(def.Filters) != 1 || def.Filters[0].Column != "StageName" ||
		def.Filters[0].Operator != "equals" || def.Filters[0].Value != "Prospecting" {
		t.Fatalf("Filters = %+v, want StageName equals Prospecting", def.Filters)
	}
	if def.DateFilter == nil || def.DateFilter.Column != "CloseDate" ||
		def.DateFilter.DurationValue != "CUSTOM" {
		t.Fatalf("DateFilter = %+v, want CloseDate custom filter", def.DateFilter)
	}
	want := []models.ReportAggregate{
		{Field: "", Type: "RowCount"},
		{Field: "AMOUNT", Type: "SUM"},
		{Field: "AGE", Type: "AVG"},
		{Field: "CLOSE_DATE", Type: "MAX"},
		{Field: "CREATED_DATE", Type: "MIN"},
	}
	if len(def.Aggregates) != len(want) {
		t.Fatalf("Aggregates = %+v, want %+v", def.Aggregates, want)
	}
	for i := range want {
		if def.Aggregates[i] != want[i] {
			t.Fatalf("Aggregates[%d] = %+v, want %+v", i, def.Aggregates[i], want[i])
		}
	}
}

func TestReportDefinitionDialectNormalizesSourceAndColumnTokens(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	source := dialect.ResolveSourceObject("CustomEntity$Issue__c@1", nil)
	if source != "Issue__c" {
		t.Fatalf("ResolveSourceObject = %q, want Issue__c", source)
	}
	if got := dialect.ResolveSourceObject("OpportunityReport", nil); got != "Opportunity" {
		t.Fatalf("ResolveSourceObject report token = %q, want Opportunity", got)
	}
	if got := dialect.NormalizeColumn("Opportunity.CLOSE_DATE"); got != "CLOSE_DATE" {
		t.Fatalf("NormalizeColumn dotted = %q, want CLOSE_DATE", got)
	}
	if got := dialect.NormalizeColumn("CLOSE_DATE"); got != "CloseDate" {
		t.Fatalf("NormalizeColumn snake = %q, want CloseDate", got)
	}
}

func TestReportDefinitionDialectNormalizesLegacyTokenShapedSourceObject(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	def, err := dialect.ParseStoredDefinition(contract.Record{
		"Id":           "00O1",
		"Name":         "Legacy Pipeline",
		"SourceObject": "OpportunityReport",
		"ReportType":   "Tabular",
	})
	if err != nil {
		t.Fatalf("ParseStoredDefinition: %v", err)
	}
	if def.SourceObject != "Opportunity" {
		t.Fatalf("SourceObject = %q, want Opportunity", def.SourceObject)
	}
	if def.SourceRef != "OpportunityReport" {
		t.Fatalf("SourceRef = %q, want OpportunityReport", def.SourceRef)
	}
	if def.DisplayMode != "TABULAR" {
		t.Fatalf("DisplayMode = %q, want TABULAR", def.DisplayMode)
	}
}

func TestReportDefinitionDialectRequestsBooleanFilterSupplementalField(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	fields := dialect.SupplementalStoredDefinitionFields()
	if len(fields) != 1 || fields[0] != "BooleanFilter" {
		t.Fatalf("SupplementalStoredDefinitionFields = %+v, want [BooleanFilter]", fields)
	}
}

func TestReportDefinitionDialectBuildsNumberedFormulaFilterExpression(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	expr, ok := dialect.BuildFilterExpression(contract.ReportFilterExpressionRequest{
		Language:      contract.ReportFilterExpressionLanguageFormula,
		BooleanFilter: "1 OR 2",
		Filters: []models.ReportFilter{
			{Column: "Status", Operator: "equals", Value: "Open"},
			{Column: "Status", Operator: "equals", Value: "Closed"},
		},
		Fragments: []contract.ReportFilterExpressionFragment{
			{Index: 1, Expression: `Status = "Open"`},
			{Index: 2, Expression: `Status = "Closed"`},
		},
	})
	if !ok {
		t.Fatal("BuildFilterExpression returned ok=false")
	}
	want := `(Status = "Open") || (Status = "Closed")`
	if expr.Expression != want {
		t.Fatalf("Expression = %q, want %q", expr.Expression, want)
	}
}

func TestReportDefinitionDialectBuildsNumberedSQLFilterExpression(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	expr, ok := dialect.BuildFilterExpression(contract.ReportFilterExpressionRequest{
		Language:      contract.ReportFilterExpressionLanguageSQL,
		BooleanFilter: "1 OR 2",
		Filters: []models.ReportFilter{
			{Column: "Status", Operator: "equals", Value: "Open"},
			{Column: "Status", Operator: "equals", Value: "Closed"},
		},
		Fragments: []contract.ReportFilterExpressionFragment{
			{Index: 1, Expression: "`issue`.`Status` = ?", Params: []any{"Open"}},
			{Index: 2, Expression: "`issue`.`Status` = ?", Params: []any{"Closed"}},
		},
	})
	if !ok {
		t.Fatal("BuildFilterExpression returned ok=false")
	}
	want := "(`issue`.`Status` = ?) OR (`issue`.`Status` = ?)"
	if expr.Expression != want {
		t.Fatalf("Expression = %q, want %q", expr.Expression, want)
	}
	if len(expr.Params) != 2 || expr.Params[0] != "Open" || expr.Params[1] != "Closed" {
		t.Fatalf("Params = %+v, want [Open Closed]", expr.Params)
	}
}

func TestReportDefinitionDialectBuildsNumberedSQLFilterExpressionWithNot(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	expr, ok := dialect.BuildFilterExpression(contract.ReportFilterExpressionRequest{
		Language:      contract.ReportFilterExpressionLanguageSQL,
		BooleanFilter: "1 OR NOT 2",
		Filters: []models.ReportFilter{
			{Column: "Status", Operator: "equals", Value: "Open"},
			{Column: "Status", Operator: "equals", Value: "Closed"},
		},
		Fragments: []contract.ReportFilterExpressionFragment{
			{Index: 1, Expression: "`issue`.`Status` = ?", Params: []any{"Open"}},
			{Index: 2, Expression: "`issue`.`Status` = ?", Params: []any{"Closed"}},
		},
	})
	if !ok {
		t.Fatal("BuildFilterExpression returned ok=false")
	}
	want := "(`issue`.`Status` = ?) OR NOT (`issue`.`Status` = ?)"
	if expr.Expression != want {
		t.Fatalf("Expression = %q, want %q", expr.Expression, want)
	}
	if len(expr.Params) != 2 || expr.Params[0] != "Open" || expr.Params[1] != "Closed" {
		t.Fatalf("Params = %+v, want [Open Closed]", expr.Params)
	}
}

func TestReportDefinitionDialectBuildsNumberedSQLFilterExpressionWithNotOnly(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	expr, ok := dialect.BuildFilterExpression(contract.ReportFilterExpressionRequest{
		Language:      contract.ReportFilterExpressionLanguageSQL,
		BooleanFilter: "Not 1",
		Filters: []models.ReportFilter{
			{Column: "Status", Operator: "equals", Value: "Closed"},
		},
		Fragments: []contract.ReportFilterExpressionFragment{
			{Index: 1, Expression: "`issue`.`Status` = ?", Params: []any{"Closed"}},
		},
	})
	if !ok {
		t.Fatal("BuildFilterExpression returned ok=false")
	}
	want := "NOT (`issue`.`Status` = ?)"
	if expr.Expression != want {
		t.Fatalf("Expression = %q, want %q", expr.Expression, want)
	}
	if len(expr.Params) != 1 || expr.Params[0] != "Closed" {
		t.Fatalf("Params = %+v, want [Closed]", expr.Params)
	}
}

func TestReportDefinitionDialectBuildsNumberedFormulaFilterExpressionWithNot(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	expr, ok := dialect.BuildFilterExpression(contract.ReportFilterExpressionRequest{
		Language:      contract.ReportFilterExpressionLanguageFormula,
		BooleanFilter: "Not 1",
		Filters: []models.ReportFilter{
			{Column: "Status", Operator: "equals", Value: "Open"},
		},
		Fragments: []contract.ReportFilterExpressionFragment{
			{Index: 1, Expression: `Status = "Open"`},
		},
	})
	if !ok {
		t.Fatal("BuildFilterExpression returned ok=false")
	}
	want := `! (Status = "Open")`
	if expr.Expression != want {
		t.Fatalf("Expression = %q, want %q", expr.Expression, want)
	}
}

func TestReportDefinitionDialectRejectsUnsafeNumberedSQLFilterExpression(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	expr, ok := dialect.BuildFilterExpression(contract.ReportFilterExpressionRequest{
		Language:      contract.ReportFilterExpressionLanguageSQL,
		BooleanFilter: "1 OR 2; DROP TABLE Report",
		Filters: []models.ReportFilter{
			{Column: "Status", Operator: "equals", Value: "Open"},
			{Column: "Status", Operator: "equals", Value: "Closed"},
		},
		Fragments: []contract.ReportFilterExpressionFragment{
			{Index: 1, Expression: "`issue`.`Status` = ?", Params: []any{"Open"}},
			{Index: 2, Expression: "`issue`.`Status` = ?", Params: []any{"Closed"}},
		},
	})
	if ok {
		t.Fatalf("BuildFilterExpression returned ok=true with expr %+v, want rejection", expr)
	}
}

func TestReportDefinitionDialectBuildsParenthesizedNumberedSQLFilterExpression(t *testing.T) {
	dialect := NewReportDefinitionDialect()

	expr, ok := dialect.BuildFilterExpression(contract.ReportFilterExpressionRequest{
		Language:      contract.ReportFilterExpressionLanguageSQL,
		BooleanFilter: "(1 or 2) and 3",
		Filters: []models.ReportFilter{
			{Column: "Status", Operator: "equals", Value: "Open"},
			{Column: "Status", Operator: "equals", Value: "Closed"},
			{Column: "Priority", Operator: "equals", Value: "High"},
		},
		Fragments: []contract.ReportFilterExpressionFragment{
			{Index: 1, Expression: "`issue`.`Status` = ?", Params: []any{"Open"}},
			{Index: 2, Expression: "`issue`.`Status` = ?", Params: []any{"Closed"}},
			{Index: 3, Expression: "`issue`.`Priority` = ?", Params: []any{"High"}},
		},
	})
	if !ok {
		t.Fatal("BuildFilterExpression returned ok=false")
	}
	want := "((`issue`.`Status` = ?) OR (`issue`.`Status` = ?)) AND (`issue`.`Priority` = ?)"
	if expr.Expression != want {
		t.Fatalf("Expression = %q, want %q", expr.Expression, want)
	}
	if len(expr.Params) != 3 || expr.Params[0] != "Open" || expr.Params[1] != "Closed" || expr.Params[2] != "High" {
		t.Fatalf("Params = %+v, want [Open Closed High]", expr.Params)
	}
}
