package analytics

import (
	"context"
	"strings"
	"testing"

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

func TestDefaultReportDefinitionDialectRejectsSalesforceAggregateKeys(t *testing.T) {
	svc := NewReportService(nil, nil, nil, nil)

	_, err := svc.parseReportDefinition(models.SObject{
		"Id":           "00O1",
		"Name":         "Pipeline",
		"SourceObject": "Opportunity",
		"Aggregates":   `["s!Amount"]`,
	})
	if err == nil {
		t.Fatal("default report definition dialect accepted Salesforce aggregate key; want explicit dialect injection")
	}
	if !strings.Contains(err.Error(), "report definition dialect") {
		t.Fatalf("error = %v, want report definition dialect guidance", err)
	}
}

func TestDefaultReportDefinitionDialectParsesNeutralStructuredAggregates(t *testing.T) {
	svc := NewReportService(nil, nil, nil, nil)

	def, err := svc.parseReportDefinition(models.SObject{
		"Id":           "00O1",
		"Name":         "Pipeline",
		"SourceObject": "Issue",
		"Filters":      `[{"column":"Status","operator":"equals","value":"Open"}]`,
		"Aggregates":   `[{"field":"Points","type":"SUM"}]`,
		"DateFilter": `{
			"column": "CreatedDate",
			"durationValue": "CUSTOM",
			"startDate": "2026-01-01",
			"endDate": "2026-01-31"
		}`,
	})
	if err != nil {
		t.Fatalf("parseReportDefinition: %v", err)
	}
	if len(def.Filters) != 1 || def.Filters[0] != (models.ReportFilter{Column: "Status", Operator: "equals", Value: "Open"}) {
		t.Fatalf("Filters = %+v, want Status equals Open", def.Filters)
	}
	if len(def.Aggregates) != 1 || def.Aggregates[0].Field != "Points" || def.Aggregates[0].Type != "SUM" {
		t.Fatalf("Aggregates = %+v, want neutral SUM(Points)", def.Aggregates)
	}
	if def.DateFilter == nil ||
		*def.DateFilter != (models.ReportDateFilter{
			Column:        "CreatedDate",
			Preset:        "CUSTOM",
			DurationValue: "CUSTOM",
			StartDate:     "2026-01-01",
			EndDate:       "2026-01-31",
		}) {
		t.Fatalf("DateFilter = %+v, want CreatedDate custom range", def.DateFilter)
	}
}

func TestDefaultReportDefinitionDialectParsesNeutralModelBoundaryFields(t *testing.T) {
	svc := NewReportService(nil, nil, nil, nil)

	def, err := svc.parseReportDefinition(models.SObject{
		"Id":           "00O1",
		"Name":         "Work By Status",
		"SourceObject": "Issue",
		"SourceRef":    "IssueWithComments",
		"DisplayMode":  "summary",
		"FilterLogic":  "StatusOpen",
		"DateFilter": `{
			"column": "CreatedDate",
			"preset": "CUSTOM",
			"startDate": "2026-01-01",
			"endDate": "2026-01-31"
		}`,
	})
	if err != nil {
		t.Fatalf("parseReportDefinition: %v", err)
	}
	if def.SourceRef != "IssueWithComments" {
		t.Fatalf("SourceRef = %q, want IssueWithComments", def.SourceRef)
	}
	if def.DisplayMode != "summary" {
		t.Fatalf("DisplayMode = %q, want summary", def.DisplayMode)
	}
	if def.FilterLogic != "StatusOpen" {
		t.Fatalf("FilterLogic = %q, want StatusOpen", def.FilterLogic)
	}
	if def.DateFilter == nil ||
		*def.DateFilter != (models.ReportDateFilter{
			Column:        "CreatedDate",
			Preset:        "CUSTOM",
			DurationValue: "CUSTOM",
			StartDate:     "2026-01-01",
			EndDate:       "2026-01-31",
		}) {
		t.Fatalf("DateFilter = %+v, want CreatedDate custom range", def.DateFilter)
	}
}

func TestDefaultReportDefinitionDialectIgnoresProductCompatibilityAliases(t *testing.T) {
	svc := NewReportService(nil, nil, nil, nil)

	def, err := svc.parseReportDefinition(models.SObject{
		"Id":            "00O1",
		"Name":          "Work By Status",
		"SourceObject":  "Issue",
		"Format":        "SUMMARY",
		"BooleanFilter": "1 OR 2",
	})
	if err != nil {
		t.Fatalf("parseReportDefinition: %v", err)
	}
	if def.DisplayMode == "SUMMARY" {
		t.Fatal("neutral dialect consumed product Format alias; want DisplayMode-only neutral boundary")
	}
	if def.FilterLogic != "" {
		t.Fatalf("FilterLogic = %q, want empty when only product BooleanFilter alias is present", def.FilterLogic)
	}
}

func TestDefaultReportDefinitionDialectIgnoresStoredReportTypeCompatibilityAlias(t *testing.T) {
	svc := NewReportService(nil, nil, nil, nil)

	def, err := svc.parseReportDefinition(models.SObject{
		"Id":           "00O1",
		"Name":         "Work By Status",
		"SourceObject": "Issue",
		"ReportType":   "IssueWithComments",
	})
	if err != nil {
		t.Fatalf("parseReportDefinition: %v", err)
	}
	if def.SourceRef != "" {
		t.Fatalf("SourceRef = %q, want empty neutral SourceRef when only legacy ReportType is present", def.SourceRef)
	}
}

func TestDefaultReportDefinitionDialectParsesStoredStandardDateFilterColumn(t *testing.T) {
	svc := NewReportService(nil, nil, nil, nil)

	def, err := svc.parseReportDefinition(models.SObject{
		"Id":           "00O1",
		"Name":         "Work By Created Date",
		"SourceObject": "Issue",
		"StandardDateFilter": `{
			"column": "CreatedDate",
			"durationValue": "CUSTOM",
			"startDate": "2026-01-01",
			"endDate": "2026-01-31"
		}`,
	})
	if err != nil {
		t.Fatalf("parseReportDefinition: %v", err)
	}
	if def.DateFilter == nil ||
		*def.DateFilter != (models.ReportDateFilter{
			Column:        "CreatedDate",
			Preset:        "CUSTOM",
			DurationValue: "CUSTOM",
			StartDate:     "2026-01-01",
			EndDate:       "2026-01-31",
		}) {
		t.Fatalf("DateFilter = %+v, want stored StandardDateFilter mapped to neutral DateFilter", def.DateFilter)
	}
}

func TestDefaultReportDefinitionDialectDefaultsGroupedStoredReportsToSummary(t *testing.T) {
	svc := NewReportService(nil, nil, nil, nil)

	def, err := svc.parseReportDefinition(models.SObject{
		"Id":           "00O1",
		"Name":         "Work By Status",
		"SourceObject": "Issue",
		"Grouping":     `["Status"]`,
		"Aggregates":   `[{"field":"Points","type":"SUM"}]`,
	})
	if err != nil {
		t.Fatalf("parseReportDefinition: %v", err)
	}
	if def.DisplayMode != "SUMMARY" {
		t.Fatalf("DisplayMode = %q, want SUMMARY for grouped stored report", def.DisplayMode)
	}
}

func TestReportExecutionModeUsesGroupingShapeNotProductFormat(t *testing.T) {
	def := &models.ReportDefinition{
		DisplayMode: "TABULAR",
	}
	if !shouldExecuteGroupedReport(def, []string{"Status"}) {
		t.Fatal("grouped report should execute through grouped path even when product format says tabular")
	}
	if shouldExecuteGroupedReport(def, nil) {
		t.Fatal("report without grouping should not execute through grouped path")
	}
}

func TestGroupedReportExecutionNormalizesReturnedFormatToSummary(t *testing.T) {
	stub := &stubQueryService{
		returnAnalytics: []models.SObject{{
			"name": "Open",
			"val":  float64(3),
		}},
	}
	svc := NewReportService(stub, nil, nil, nil)

	result, err := svc.ExecuteReportDefinition(context.Background(), &models.ReportDefinition{
		Name:         "Work By Status",
		SourceObject: "Issue",
		DisplayMode:  "TABULAR",
		Grouping:     []string{"Status"},
		Aggregates:   []models.ReportAggregate{{Field: "Points", Type: "SUM"}},
	}, nil, nil)
	if err != nil {
		t.Fatalf("ExecuteReportDefinition: %v", err)
	}
	if stub.analyticsCallCount != 1 {
		t.Fatalf("RunAnalytics calls = %d, want 1", stub.analyticsCallCount)
	}
	if result.ReportMetadata.DisplayMode != "SUMMARY" {
		t.Fatalf("DisplayMode = %q, want SUMMARY for grouped execution", result.ReportMetadata.DisplayMode)
	}
	if len(result.ReportMetadata.Grouping) != 1 || result.ReportMetadata.Grouping[0] != "Status" {
		t.Fatalf("Grouping = %+v, want [Status]", result.ReportMetadata.Grouping)
	}
}

func TestGroupedJoinReportExecutionNormalizesReturnedFormatToSummary(t *testing.T) {
	stub := &stubQueryService{
		returnRawRows: []models.SObject{{
			"Status": "Open",
		}},
	}
	svc := NewReportService(stub, nil, nil, nil, WithCRTResolver(fixedCRTResolver{tmpl: &JoinTemplate{
		PrimaryTable: "Issue",
		PrimaryAlias: "i",
		Columns: map[string]QualifiedColumn{
			"Status": {TableAlias: "i", Column: "Status"},
		},
	}}))

	result, err := svc.ExecuteReportDefinition(context.Background(), &models.ReportDefinition{
		Name:         "Cross Object Work By Status",
		SourceObject: "IssueWithChildren",
		DisplayMode:  "TABULAR",
		Grouping:     []string{"Status"},
	}, nil, nil)
	if err != nil {
		t.Fatalf("ExecuteReportDefinition: %v", err)
	}
	if stub.rawCallCount != 1 {
		t.Fatalf("ExecuteRawSQL calls = %d, want 1", stub.rawCallCount)
	}
	if result.ReportMetadata.DisplayMode != "SUMMARY" {
		t.Fatalf("DisplayMode = %q, want SUMMARY for grouped JOIN execution", result.ReportMetadata.DisplayMode)
	}
	if len(result.ReportMetadata.Grouping) != 1 || result.ReportMetadata.Grouping[0] != "Status" {
		t.Fatalf("Grouping = %+v, want [Status]", result.ReportMetadata.Grouping)
	}
}

func TestReportExecutionUsesNeutralSourceRefForCRTDispatch(t *testing.T) {
	stub := &stubQueryService{
		returnRawRows: []models.SObject{{
			"Status": "Open",
		}},
	}
	resolver := &sourceRecordingCRTResolver{
		match: "IssueWithChildren",
		tmpl: &JoinTemplate{
			PrimaryTable: "Issue",
			PrimaryAlias: "i",
			Columns: map[string]QualifiedColumn{
				"Status": {TableAlias: "i", Column: "Status"},
			},
		},
	}
	svc := NewReportService(stub, nil, nil, nil, WithCRTResolver(resolver))

	_, err := svc.ExecuteReportDefinition(context.Background(), &models.ReportDefinition{
		Name:         "Cross Object Work By Status",
		SourceObject: "Issue",
		SourceRef:    "IssueWithChildren",
		DisplayMode:  "tabular",
		Grouping:     []string{"Status"},
	}, nil, nil)
	if err != nil {
		t.Fatalf("ExecuteReportDefinition: %v", err)
	}
	if len(resolver.sources) != 1 || resolver.sources[0] != "IssueWithChildren" {
		t.Fatalf("CRT resolver sources = %+v, want [IssueWithChildren]", resolver.sources)
	}
	if stub.rawCallCount != 1 {
		t.Fatalf("ExecuteRawSQL calls = %d, want 1", stub.rawCallCount)
	}
}

func TestReportExecutionSourceTokenUsesSourceRefLiterally(t *testing.T) {
	def := &models.ReportDefinition{
		SourceObject: "Issue",
		SourceRef:    "SUMMARY",
	}

	if got := reportExecutionSourceToken(def); got != "SUMMARY" {
		t.Fatalf("reportExecutionSourceToken = %q, want literal SourceRef SUMMARY", got)
	}
}

func TestDefaultReportDefinitionDialectDoesNotBuildNumberedBooleanFilterExpression(t *testing.T) {
	svc := NewReportService(nil, nil, nil, nil)

	if expr, ok := svc.reportDefinitionDialect().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"},
		},
	}); ok {
		t.Fatalf("default report definition dialect built expression %+v; want explicit dialect injection", expr)
	}
}

func TestReportServiceDelegatesFilterExpressionCompilationToDialect(t *testing.T) {
	dialect := &delegatingReportDialect{}
	svc := NewReportService(nil, nil, nil, nil, WithReportDefinitionDialect(dialect))

	expr := svc.compileReportFilterExpression(context.Background(), &models.ReportDefinition{
		FilterLogic: "1 OR 2",
		Filters: []models.ReportFilter{
			{Column: "Status", Operator: "equals", Value: "Open"},
			{Column: "Owner.Name", Operator: "contains", Value: "Ada"},
		},
	}, nil, nil, "Issue", map[int]string{1: `OwnerId in ["005-owner"]`})

	if expr != "delegated_expression" {
		t.Fatalf("compiled expression = %q, want delegated_expression", expr)
	}
	if dialect.request.Language != contract.ReportFilterExpressionLanguageFormula {
		t.Fatalf("language = %q, want formula", dialect.request.Language)
	}
	if dialect.request.BooleanFilter != "1 OR 2" {
		t.Fatalf("BooleanFilter = %q, want 1 OR 2", dialect.request.BooleanFilter)
	}
	if len(dialect.request.Fragments) != 2 {
		t.Fatalf("fragments = %+v, want two fragments", dialect.request.Fragments)
	}
	if dialect.request.Fragments[1].Expression != `OwnerId in ["005-owner"]` {
		t.Fatalf("lookup fragment = %q, want pre-resolved lookup fragment", dialect.request.Fragments[1].Expression)
	}
}

type delegatingReportDialect struct {
	NeutralReportDefinitionDialect
	request contract.ReportFilterExpressionRequest
}

func (d *delegatingReportDialect) BuildFilterExpression(req contract.ReportFilterExpressionRequest) (contract.ReportFilterExpression, bool) {
	d.request = req
	return contract.ReportFilterExpression{Expression: "delegated_expression"}, true
}

type fixedCRTResolver struct {
	tmpl *JoinTemplate
}

func (r fixedCRTResolver) Resolve(context.Context, string, *models.UserSession) (*JoinTemplate, []CRTWarning, error) {
	if r.tmpl == nil {
		return nil, nil, nil
	}
	return r.tmpl, nil, nil
}

type sourceRecordingCRTResolver struct {
	match   string
	tmpl    *JoinTemplate
	sources []string
}

func (r *sourceRecordingCRTResolver) Resolve(_ context.Context, source string, _ *models.UserSession) (*JoinTemplate, []CRTWarning, error) {
	r.sources = append(r.sources, source)
	if source != r.match {
		return nil, nil, nil
	}
	return r.tmpl, nil, nil
}
