package analytics

import (
	"context"
	"errors"
	"strings"
	"testing"

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

func TestExecuteSubQueriesFailsClosedWhenRelationshipCannotResolve(t *testing.T) {
	missingName := "OtherChildren"
	tests := []struct {
		name              string
		includeChild      bool
		childRelationship *string
		wantInternal      bool
		emptyParents      bool
	}{
		{name: "no candidates"},
		{name: "no candidates on an empty root page", emptyParents: true},
		{name: "authoritative no match", includeChild: true, childRelationship: &missingName},
		{name: "metadata not converged", includeChild: true, wantInternal: true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parent, child := subqueryFailClosedSchemas(tt.childRelationship)
			schemas := []*models.ObjectMetadata{parent}
			if tt.includeChild {
				schemas = append(schemas, child)
			}
			repo := &subqueryFailClosedRepository{}
			service := NewQueryService(repo, &subqueryFailClosedMetadata{schemas: schemas}, &subqueryFailClosedPermissions{}, nil)

			parents := []models.SObject{{constants.FieldID: "parent-1"}}
			if tt.emptyParents {
				parents = nil
			}
			_, err := service.executeSubQueries(
				context.Background(),
				parents,
				parent,
				[]models.SubQueryRequest{{RelationshipName: "Children", SelectFields: []string{constants.FieldName}}},
				&models.UserSession{ID: "user-1"},
				false,
			)
			if err == nil {
				t.Fatal("executeSubQueries error = nil, want fail-closed relationship error")
			}
			if tt.wantInternal {
				var internal *appErrors.InternalError
				if !errors.As(err, &internal) {
					t.Fatalf("executeSubQueries error = %T %v, want internal metadata failure", err, err)
				}
			} else {
				var validation *appErrors.ValidationError
				if !errors.As(err, &validation) {
					t.Fatalf("executeSubQueries error = %T %v, want invalid relationship", err, err)
				}
			}
			if repo.calls != 0 {
				t.Fatalf("repository calls = %d, want 0 for unresolved relationship", repo.calls)
			}
		})
	}
}

func TestExecuteSubQueriesDoesNotReadChildrenForAValidEmptyRootPage(t *testing.T) {
	childRelationship := "Children"
	parent, child := subqueryFailClosedSchemas(&childRelationship)
	repo := &subqueryFailClosedRepository{}
	service := NewQueryService(repo, &subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}}, &subqueryFailClosedPermissions{}, nil)

	got, err := service.executeSubQueries(
		context.Background(),
		nil,
		parent,
		[]models.SubQueryRequest{{RelationshipName: childRelationship, SelectFields: []string{constants.FieldName}}},
		&models.UserSession{ID: "user-1"},
		false,
	)
	if err != nil {
		t.Fatalf("executeSubQueries: %v", err)
	}
	if len(got) != 0 {
		t.Fatalf("empty root page = %#v, want no rows", got)
	}
	if repo.calls != 0 {
		t.Fatalf("repository calls = %d, want 0 without parent rows", repo.calls)
	}
}

func TestExecuteSubQueriesPropagatesChildObjectReadDenial(t *testing.T) {
	childRelationship := "Children"
	parent, child := subqueryFailClosedSchemas(&childRelationship)
	repo := &subqueryFailClosedRepository{}
	service := NewQueryService(
		repo,
		&subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}},
		&subqueryFailClosedPermissions{deniedObject: child.APIName},
		nil,
	)

	_, err := service.executeSubQueries(
		context.Background(),
		[]models.SObject{{constants.FieldID: "parent-1"}},
		parent,
		[]models.SubQueryRequest{{RelationshipName: childRelationship, SelectFields: []string{constants.FieldName}}},
		&models.UserSession{ID: "user-1"},
		false,
	)
	var denied *appErrors.PermissionError
	if !errors.As(err, &denied) {
		t.Fatalf("executeSubQueries error = %T %v, want child object permission denial", err, err)
	}
	if repo.calls != 0 {
		t.Fatalf("repository calls = %d, want 0 after permission denial", repo.calls)
	}
}

func TestExecuteSubQueriesFailsWhenResolvedChildSchemaIsUnavailable(t *testing.T) {
	childRelationship := "Children"
	parent, child := subqueryFailClosedSchemas(&childRelationship)
	repo := &subqueryFailClosedRepository{}
	service := NewQueryService(
		repo,
		&subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}, unavailable: map[string]bool{strings.ToLower(child.APIName): true}},
		&subqueryFailClosedPermissions{},
		nil,
	)

	_, err := service.executeSubQueries(
		context.Background(),
		[]models.SObject{{constants.FieldID: "parent-1"}},
		parent,
		[]models.SubQueryRequest{{RelationshipName: childRelationship, SelectFields: []string{constants.FieldName}}},
		&models.UserSession{ID: "user-1"},
		false,
	)
	var internal *appErrors.InternalError
	if !errors.As(err, &internal) {
		t.Fatalf("executeSubQueries error = %T %v, want internal child schema failure", err, err)
	}
	if repo.calls != 0 {
		t.Fatalf("repository calls = %d, want 0 without child schema", repo.calls)
	}
}

func TestExecuteSubQueriesPropagatesChildRepositoryFailure(t *testing.T) {
	childRelationship := "Children"
	parent, child := subqueryFailClosedSchemas(&childRelationship)
	wantErr := errors.New("child repository unavailable")
	repo := &subqueryFailClosedRepository{err: wantErr}
	service := NewQueryService(repo, &subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}}, &subqueryFailClosedPermissions{}, nil)

	_, err := service.executeSubQueries(
		context.Background(),
		[]models.SObject{{constants.FieldID: "parent-1"}},
		parent,
		[]models.SubQueryRequest{{RelationshipName: childRelationship, SelectFields: []string{constants.FieldName}}},
		&models.UserSession{ID: "user-1"},
		false,
	)
	if !errors.Is(err, wantErr) {
		t.Fatalf("executeSubQueries error = %v, want wrapped repository failure", err)
	}
	if repo.calls != 1 {
		t.Fatalf("repository calls = %d, want 1", repo.calls)
	}
}

func TestExecuteSubQueriesKeepsSuccessfulEmptyRelationshipExplicit(t *testing.T) {
	childRelationship := "Children"
	parent, child := subqueryFailClosedSchemas(&childRelationship)
	repo := &subqueryFailClosedRepository{result: &models.QueryResult{Done: true, Records: []models.SObject{}}}
	service := NewQueryService(repo, &subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}}, &subqueryFailClosedPermissions{}, nil)

	got, err := service.executeSubQueries(
		context.Background(),
		[]models.SObject{{constants.FieldID: "parent-1"}},
		parent,
		[]models.SubQueryRequest{{RelationshipName: childRelationship, SelectFields: []string{constants.FieldName}}},
		&models.UserSession{ID: "user-1"},
		false,
	)
	if err != nil {
		t.Fatalf("executeSubQueries: %v", err)
	}
	children, ok := got[0][childRelationship].([]models.SObject)
	if !ok || children == nil || len(children) != 0 {
		t.Fatalf("successful empty relationship = %#v, want explicit empty []models.SObject", got[0][childRelationship])
	}
}

func TestExecuteSubQueriesUsesHiddenForeignKeyOnlyAsInternalDependency(t *testing.T) {
	childRelationship := "Children"
	parent, child := subqueryFailClosedSchemas(&childRelationship)
	repo := &subqueryFailClosedRepository{result: &models.QueryResult{Done: true, Records: []models.SObject{{
		constants.FieldID:   "child-1",
		"ParentId":          "parent-1",
		constants.FieldName: "visible child",
	}}}}
	service := NewQueryService(
		repo,
		&subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}},
		&subqueryFailClosedPermissions{hiddenFields: map[string]bool{strings.ToLower(child.APIName + ".ParentId"): true}},
		nil,
	)
	ctx := constants.WithSecurityContext(context.Background(), constants.UserContext, "subquery FLS")

	got, err := service.executeSubQueries(
		ctx,
		[]models.SObject{{constants.FieldID: "parent-1"}},
		parent,
		[]models.SubQueryRequest{{RelationshipName: childRelationship, SelectFields: []string{constants.FieldName, "ParentId"}}},
		&models.UserSession{ID: "user-1"},
		false,
	)
	if err != nil {
		t.Fatalf("executeSubQueries: %v", err)
	}
	if !containsFold(repo.fields, "ParentId") {
		t.Fatalf("repository fields = %#v, want FLS-hidden relationship FK as internal grouping dependency", repo.fields)
	}
	children, ok := got[0][childRelationship].([]models.SObject)
	if !ok || len(children) != 1 {
		t.Fatalf("children = %#v, want one grouped child", got[0][childRelationship])
	}
	if children[0][constants.FieldName] != "visible child" {
		t.Fatalf("child = %#v, want visible Name", children[0])
	}
	if _, leaked := children[0]["ParentId"]; leaked {
		t.Fatalf("FLS-hidden internal FK leaked into response: %#v", children[0])
	}
}

func TestExecuteSubQueriesDoesNotReadBlockedGroupingField(t *testing.T) {
	childRelationship := "Children"
	parent, child := subqueryFailClosedSchemas(&childRelationship)
	repo := &subqueryFailClosedRepository{}
	service := NewQueryService(
		repo,
		&subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}},
		&subqueryFailClosedPermissions{blockedFields: map[string]bool{strings.ToLower(child.APIName + ".ParentId"): true}},
		nil,
	)

	_, err := service.executeSubQueries(
		context.Background(),
		[]models.SObject{{constants.FieldID: "parent-1"}},
		parent,
		[]models.SubQueryRequest{{RelationshipName: childRelationship, SelectFields: []string{constants.FieldName}}},
		&models.UserSession{ID: "user-1"},
		false,
	)
	var denied *appErrors.PermissionError
	if !errors.As(err, &denied) {
		t.Fatalf("executeSubQueries error = %T %v, want blocked grouping-field denial", err, err)
	}
	if repo.calls != 0 {
		t.Fatalf("repository calls = %d, want 0 for read-blocked grouping field", repo.calls)
	}
}

func TestQueryWithSpecRejectsAggregateAndChildSubqueryCombination(t *testing.T) {
	childRelationship := "Children"
	parent, child := subqueryFailClosedSchemas(&childRelationship)
	repo := &subqueryFailClosedRepository{}
	service := NewQueryService(repo, &subqueryFailClosedMetadata{schemas: []*models.ObjectMetadata{parent, child}}, &subqueryFailClosedPermissions{}, nil)

	_, err := service.QueryWithSpec(context.Background(), queryspec.QuerySpec{
		ObjectAPIName: parent.APIName,
		SelectFields:  []string{"COUNT(Id)"},
		SubQueries:    []queryspec.SubQuerySpec{{RelationshipName: childRelationship, SelectFields: []string{constants.FieldName}}},
	}, &models.UserSession{ID: "user-1"})
	var validation *appErrors.ValidationError
	if !errors.As(err, &validation) {
		t.Fatalf("QueryWithSpec error = %T %v, want invalid aggregate/subquery plan", err, err)
	}
	if repo.calls != 0 {
		t.Fatalf("repository calls = %d, want 0 for invalid aggregate/subquery plan", repo.calls)
	}
}

type subqueryFailClosedMetadata struct {
	MetadataSource
	schemas     []*models.ObjectMetadata
	unavailable map[string]bool
}

func (m *subqueryFailClosedMetadata) GetSchema(_ context.Context, apiName string) *models.ObjectMetadata {
	if m.unavailable[strings.ToLower(apiName)] {
		return nil
	}
	for _, schema := range m.schemas {
		if schema != nil && strings.EqualFold(schema.APIName, apiName) {
			return schema
		}
	}
	return nil
}

func (m *subqueryFailClosedMetadata) GetSchemas(context.Context) []*models.ObjectMetadata {
	return append([]*models.ObjectMetadata(nil), m.schemas...)
}

func (*subqueryFailClosedMetadata) GetSystemFields(_ context.Context, _ string) []string {
	return []string{constants.FieldID, constants.FieldName}
}

type subqueryFailClosedPermissions struct {
	PermissionChecker
	deniedObject  string
	hiddenFields  map[string]bool
	blockedFields map[string]bool
}

func (p *subqueryFailClosedPermissions) CheckObjectPermissionWithUser(_ context.Context, objectAPIName, _ string, _ *models.UserSession) bool {
	return !strings.EqualFold(p.deniedObject, objectAPIName)
}

func (p *subqueryFailClosedPermissions) CheckFieldVisibilityWithUser(_ context.Context, objectAPIName, fieldAPIName string, _ *models.UserSession) (bool, error) {
	return !p.hiddenFields[strings.ToLower(objectAPIName+"."+fieldAPIName)], nil
}

func (*subqueryFailClosedPermissions) CheckRecordAccess(context.Context, *models.ObjectMetadata, models.SObject, string, *models.UserSession) bool {
	return true
}

func (*subqueryFailClosedPermissions) PrimeReadAccessInputCache(context.Context, string, []models.SObject, *models.UserSession) {
}

func (*subqueryFailClosedPermissions) BuildRLSResult(context.Context, *models.ObjectMetadata, string, *models.UserSession) *domainrls.Result {
	return nil
}

func (p *subqueryFailClosedPermissions) IsFieldReadBlocked(_ context.Context, objectAPIName, fieldAPIName string) bool {
	return p.blockedFields[strings.ToLower(objectAPIName+"."+fieldAPIName)]
}

func (*subqueryFailClosedPermissions) IsBlockedObject(context.Context, string, string) bool {
	return false
}

type subqueryFailClosedRepository struct {
	ports.QueryRepository
	result *models.QueryResult
	err    error
	calls  int
	fields []string
}

func (r *subqueryFailClosedRepository) Find(_ context.Context, _ *models.ObjectMetadata, _ models.QueryRequest, _ *domainrls.Result, fields []string) (*models.QueryResult, error) {
	r.calls++
	r.fields = append([]string(nil), fields...)
	if r.err != nil {
		return nil, r.err
	}
	if r.result == nil {
		return &models.QueryResult{Done: true, Records: []models.SObject{}}, nil
	}
	return r.result, nil
}

func (*subqueryFailClosedRepository) GetRecordsByIDs(context.Context, *models.ObjectMetadata, []string, []string) ([]models.SObject, error) {
	return []models.SObject{}, nil
}

func subqueryFailClosedSchemas(childRelationshipName *string) (*models.ObjectMetadata, *models.ObjectMetadata) {
	relationshipName := "Parent"
	parent := &models.ObjectMetadata{
		APIName: "Parent",
		Fields: []models.FieldMetadata{{
			APIName: constants.FieldID, Type: constants.FieldTypeString, PhysicalName: "id", IsSystem: true,
		}},
	}
	child := &models.ObjectMetadata{
		APIName: "Child",
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, PhysicalName: "id", IsSystem: true},
			{APIName: constants.FieldName, Type: constants.FieldTypeString, PhysicalName: "name", IsNameField: true},
			{
				APIName: "ParentId", Type: constants.FieldTypeReference, PhysicalName: "parent_id",
				ReferenceTo: []string{parent.APIName}, RelationshipName: &relationshipName, ChildRelationshipName: childRelationshipName,
			},
		},
	}
	return parent, child
}

func containsFold(values []string, target string) bool {
	for _, value := range values {
		if strings.EqualFold(value, target) {
			return true
		}
	}
	return false
}
