package analytics

import (
	"context"
	"errors"
	"testing"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	domainrls "github.com/nexuscrm/kernel/pkg/rls"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

// Explicit selection errors must fail independently of row presence, before
// storage execution can turn a misspelled field into a successful partial row.
func TestQueryRejectsUnknownSelectionsBeforeStorage(t *testing.T) {
	rel := "Children"
	parent, child := subqueryFailClosedSchemas(&rel)
	for _, selection := range []string{"Missing", "Missing_Name", "Name_Name", "Missing.Name", "Name.Id", "Parent.Missing", "Parent.Id.More", "Parent.", ".Id"} {
		t.Run(selection, func(t *testing.T) {
			repo := &subqueryFailClosedRepository{}
			service := NewQueryService(repo, &subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}}, &subqueryFailClosedPermissions{}, nil)
			_, err := service.QueryWithSpec(context.Background(), queryspec.QuerySpec{ObjectAPIName: child.APIName, SelectFields: []string{"Id", selection}}, &models.UserSession{ID: "reader"})
			var invalid *appErrors.ValidationError
			if !errors.As(err, &invalid) || invalid.Field != "SelectFields" {
				t.Fatalf("query error = %v, want selection validation failure", err)
			}
			if repo.calls != 0 {
				t.Fatalf("storage calls = %d, want no execution for invalid selection", repo.calls)
			}
		})
	}
}

func TestSubqueryRejectsUnknownSelectionsOnEmptyAndNonemptyRootPages(t *testing.T) {
	rel := "Children"
	parent, child := subqueryFailClosedSchemas(&rel)
	for _, selection := range []string{"Missing", "Missing.Name", "Parent.Missing"} {
		for _, empty := range []bool{false, true} {
			repo := &subqueryFailClosedRepository{}
			service := NewQueryService(repo, &subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}}, &subqueryFailClosedPermissions{}, nil)
			rows := []models.SObject{{"Id": "parent-1"}}
			if empty {
				rows = nil
			}
			_, err := service.executeSubQueries(context.Background(), rows, parent, []models.SubQueryRequest{{RelationshipName: rel, SelectFields: []string{selection}}}, &models.UserSession{ID: "reader"}, false)
			var invalid *appErrors.ValidationError
			if !errors.As(err, &invalid) || invalid.Field != "SelectFields" {
				t.Fatalf("selection %s empty=%v error=%v, want selection failure", selection, empty, err)
			}
			if repo.calls != 0 {
				t.Fatalf("storage calls = %d, want no child execution", repo.calls)
			}
		}
	}
}

func TestSelectionValidationPreservesSchemaFieldsAndHydrationAliases(t *testing.T) {
	rel := "Children"
	parent, child := subqueryFailClosedSchemas(&rel)
	child.Fields = append(child.Fields, models.FieldMetadata{APIName: "VirtualValue", IsVirtual: true}, models.FieldMetadata{APIName: "CompoundValue", IsCompound: true})
	for _, selection := range []string{"id", "NAME", "VirtualValue", "CompoundValue", "ParentId_Name", "Parent.Id", "parent.id"} {
		if err := validateQuerySelections(child, []string{selection}, func(string) *models.ObjectMetadata { return parent }); err != nil {
			t.Errorf("supported %s rejected: %v", selection, err)
		}
	}
}

func TestQueryAggregateSelectionsFailBeforeStorage(t *testing.T) {
	rel := "Children"
	parent, child := subqueryFailClosedSchemas(&rel)
	for _, fields := range [][]string{{"SUM(Missing)"}, {"COUNT(Missing)"}, {"COUNT(Id)", "SUM(Missing)"}, {"MAX(Parent.Missing)"}} {
		repo := &selectionAggregateRepository{}
		service := NewQueryService(repo, &subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}}, &subqueryFailClosedPermissions{}, nil)
		_, err := service.QueryWithSpec(context.Background(), queryspec.QuerySpec{ObjectAPIName: child.APIName, SelectFields: fields}, &models.UserSession{ID: "reader"})
		var invalid *appErrors.ValidationError
		if !errors.As(err, &invalid) || invalid.Field != "SelectFields" {
			t.Fatalf("selection %v: %v, want invalid selection", fields, err)
		}
		if repo.aggregateCalls != 0 {
			t.Fatalf("selection %v reached aggregate storage", fields)
		}
	}
}

type selectionAggregateRepository struct {
	subqueryFailClosedRepository
	aggregateCalls int
}

func (r *selectionAggregateRepository) RunAnalytics(context.Context, *models.ObjectMetadata, models.AnalyticsQuery, *domainrls.Result) (interface{}, error) {
	r.aggregateCalls++
	return 1, nil
}
