package analytics

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/permissions"
	"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"
)

func TestProcessInstanceStepsAndWorkitemsUsesGenericTargetRecordRLS(t *testing.T) {
	processInstanceSchema := processInstanceHistorySubqueryProcessInstanceSchema()
	repo := &processInstanceHistorySubqueryRepo{t: t}
	metadataSvc := processInstanceHistorySubqueryMetadataService(processInstanceSchema, processInstanceHistorySubqueryHistorySchema(), processInstanceHistorySubqueryTargetSchema())
	permissionSvc := permissions.NewPermissionService(&processInstanceHistorySubqueryPermissionRepo{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)

	parentRecords := []models.SObject{
		{constants.FieldID: "pi-readable"},
		{constants.FieldID: "pi-denied"},
	}
	ctx := constants.WithSecurityContext(context.Background(), constants.UserContext, "test user subquery")
	got, err := qs.executeSubQueries(ctx, parentRecords, processInstanceSchema, []models.SubQueryRequest{{
		RelationshipName: "StepsAndWorkitems",
		SelectFields:     []string{constants.FieldProcessInstanceHistory_StepStatus},
		Criteria: []models.QueryCriterion{{
			Field: constants.FieldProcessInstanceHistory_StepStatus,
			Op:    "=",
			Val:   constants.ApprovalStatusApproved,
		}},
		Limit: 10,
	}}, &models.UserSession{ID: "005-history-reader", ProfileID: "00e-standard"}, false)
	if err != nil {
		t.Fatalf("executeSubQueries: %v", err)
	}

	// App-blind L0 now attaches the neutral child-records slice directly under
	// the relationship name (no SF {totalSize,done,records} envelope; the dialect
	// re-wraps that). Assert the neutral []models.SObject shape precisely.
	readableRecords, ok := got[0]["StepsAndWorkitems"].([]models.SObject)
	if !ok {
		t.Fatalf("readable parent child = %#v, want neutral []models.SObject slice", got[0]["StepsAndWorkitems"])
	}
	if len(readableRecords) != 1 {
		t.Fatalf("readable parent child result = %#v, want one history row", readableRecords)
	}
	if readableRecords[0][constants.FieldProcessInstanceHistory_StepStatus] != constants.ApprovalStatusApproved {
		t.Fatalf("history StepStatus = %#v, want Approved", readableRecords[0][constants.FieldProcessInstanceHistory_StepStatus])
	}

	deniedRecords, ok := got[1]["StepsAndWorkitems"].([]models.SObject)
	if !ok {
		t.Fatalf("denied parent child = %#v, want neutral []models.SObject slice", got[1]["StepsAndWorkitems"])
	}
	if len(deniedRecords) != 0 {
		t.Fatalf("denied parent child result = %#v, want empty history rows (empty slice)", deniedRecords)
	}

	if repo.historySecurityContext != constants.UserContext {
		t.Fatalf("history query security context = %v, want UserContext", repo.historySecurityContext)
	}
	if repo.historyFilter != "" {
		t.Fatalf("history legacy filter = %q, want typed criteria relationship query", repo.historyFilter)
	}
	if !queryCriteriaContainsINValues(repo.historyCriteria, constants.FieldProcessInstanceHistory_ProcessInstanceID, "pi-readable", "pi-denied") {
		t.Fatalf("history criteria = %#v, want relationship query across both parent ids", repo.historyCriteria)
	}
	if !queryCriteriaContainsEqualValue(repo.historyCriteria, constants.FieldProcessInstanceHistory_StepStatus, constants.ApprovalStatusApproved) {
		t.Fatalf("history criteria = %#v, want child filter criterion", repo.historyCriteria)
	}
	if repo.historyRLS == nil || len(repo.historyRLS.TargetRecordFilters) == 0 {
		t.Fatalf("history RLS = %#v, want generic target-record filters", repo.historyRLS)
	}

	filterSQL := domainrls.ToSQLFilter(repo.historyRLS, constants.TableProcessInstanceHistory)
	for _, want := range []string{"TargetObjectType", "TargetObjectId", "GenericObject", "OwnerId", "005-history-reader"} {
		if !strings.Contains(filterSQL, want) {
			t.Fatalf("target-record RLS SQL missing %q:\n%s", want, filterSQL)
		}
	}
}

func TestProcessInstanceStepsAndWorkitemsProjectsHistoryFieldVisibility(t *testing.T) {
	processInstanceSchema := processInstanceHistorySubqueryProcessInstanceSchema()
	repo := &processInstanceHistorySubqueryRepo{t: t}
	metadataSvc := processInstanceHistorySubqueryMetadataService(processInstanceSchema, processInstanceHistorySubqueryHistorySchema(), processInstanceHistorySubqueryTargetSchema())
	permissionSvc := permissions.NewPermissionService(&processInstanceHistorySubqueryPermissionRepo{denyHistoryComments: true}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)

	parentRecords := []models.SObject{{constants.FieldID: "pi-readable"}}
	ctx := constants.WithSecurityContext(context.Background(), constants.UserContext, "test user subquery")
	got, err := qs.executeSubQueries(ctx, parentRecords, processInstanceSchema, []models.SubQueryRequest{{
		RelationshipName: "StepsAndWorkitems",
		SelectFields: []string{
			constants.FieldProcessInstanceHistory_StepStatus,
			constants.FieldProcessInstanceHistory_Comments,
		},
		Limit: 10,
	}}, &models.UserSession{ID: "005-history-reader", ProfileID: "00e-standard"}, false)
	if err != nil {
		t.Fatalf("executeSubQueries: %v", err)
	}

	records, ok := got[0]["StepsAndWorkitems"].([]models.SObject)
	if !ok {
		t.Fatalf("child = %#v, want neutral []models.SObject slice", got[0]["StepsAndWorkitems"])
	}
	if len(records) != 1 {
		t.Fatalf("child records = %#v, want one history row", records)
	}
	if _, ok := records[0][constants.FieldProcessInstanceHistory_Comments]; ok {
		t.Fatalf("history row leaked FLS-denied Comments: %#v", records[0])
	}
	if records[0][constants.FieldProcessInstanceHistory_StepStatus] != constants.ApprovalStatusApproved {
		t.Fatalf("history StepStatus = %#v, want Approved", records[0][constants.FieldProcessInstanceHistory_StepStatus])
	}
}

func TestProcessInstanceStepsAndWorkitemsHydratesActorNames(t *testing.T) {
	processInstanceSchema := processInstanceHistorySubqueryProcessInstanceSchema()
	repo := &processInstanceHistorySubqueryRepo{t: t}
	metadataSvc := processInstanceHistorySubqueryMetadataService(
		processInstanceSchema,
		processInstanceHistorySubqueryHistorySchema(),
		processInstanceHistorySubqueryTargetSchema(),
		processInstanceHistorySubqueryUserSchema(),
	)
	permissionSvc := permissions.NewPermissionService(&processInstanceHistorySubqueryPermissionRepo{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)

	parentRecords := []models.SObject{{constants.FieldID: "pi-readable"}}
	ctx := constants.WithSecurityContext(context.Background(), constants.UserContext, "test user subquery")
	got, err := qs.executeSubQueries(ctx, parentRecords, processInstanceSchema, []models.SubQueryRequest{{
		RelationshipName: "StepsAndWorkitems",
		SelectFields: []string{
			constants.FieldID,
			constants.FieldProcessInstanceHistory_ActorID,
			constants.FieldProcessInstanceHistory_OriginalActorID,
		},
		Limit: 10,
	}}, &models.UserSession{ID: "005-history-reader", ProfileID: "00e-standard"}, false)
	if err != nil {
		t.Fatalf("executeSubQueries: %v", err)
	}

	records, ok := got[0]["StepsAndWorkitems"].([]models.SObject)
	if !ok {
		t.Fatalf("child = %#v, want neutral []models.SObject slice", got[0]["StepsAndWorkitems"])
	}
	if len(records) != 1 {
		t.Fatalf("child records = %#v, want one history row", records)
	}
	if records[0][constants.FieldProcessInstanceHistory_ActorID+"_Name"] != "Ada Approver" {
		t.Fatalf("ActorId_Name = %#v, want Ada Approver; row=%#v", records[0][constants.FieldProcessInstanceHistory_ActorID+"_Name"], records[0])
	}
	if records[0][constants.FieldProcessInstanceHistory_OriginalActorID+"_Name"] != "Original Owner" {
		t.Fatalf("OriginalActorId_Name = %#v, want Original Owner; row=%#v", records[0][constants.FieldProcessInstanceHistory_OriginalActorID+"_Name"], records[0])
	}
}

type processInstanceHistorySubqueryRepo struct {
	ports.QueryRepository

	t                      *testing.T
	historySecurityContext constants.SecurityContext
	historyFilter          string
	historyCriteria        []models.QueryCriterion
	historyRLS             *domainrls.Result
}

func (r *processInstanceHistorySubqueryRepo) GetRecordsByIDs(ctx context.Context, schema *models.ObjectMetadata, ids []string, fields []string) ([]models.SObject, error) {
	switch schema.APIName {
	case constants.TableUser:
		rows := make([]models.SObject, 0, len(ids))
		for _, id := range ids {
			switch id {
			case "005-actor":
				rows = append(rows, models.SObject{
					constants.FieldID:        "005-actor",
					constants.FieldFirstName: "Ada",
					constants.FieldLastName:  "Approver",
					constants.FieldUsername:  "ada@example.test",
				})
			case "005-original":
				rows = append(rows, models.SObject{
					constants.FieldID:        "005-original",
					constants.FieldFirstName: "Original",
					constants.FieldLastName:  "Owner",
					constants.FieldUsername:  "original@example.test",
				})
			}
		}
		return rows, nil
	default:
		return nil, fmt.Errorf("unexpected GetRecordsByIDs schema %s", schema.APIName)
	}
}

func (r *processInstanceHistorySubqueryRepo) Find(ctx context.Context, schema *models.ObjectMetadata, req models.QueryRequest, rlsResult *domainrls.Result, visibleFields []string) (*models.QueryResult, error) {
	switch schema.APIName {
	case constants.TableProcessInstanceHistory:
		r.historySecurityContext = constants.GetSecurityContext(ctx)
		r.historyFilter = req.FilterExpr
		r.historyCriteria = append([]models.QueryCriterion(nil), req.Criteria...)
		r.historyRLS = rlsResult
		if r.t != nil {
			if rlsResult == nil || len(rlsResult.TargetRecordFilters) == 0 {
				r.t.Fatalf("ProcessInstanceHistory subquery RLS = %#v, want target-record filter", rlsResult)
			}
		}
		row := models.SObject{
			constants.FieldID: "pih-readable",
			constants.FieldProcessInstanceHistory_ProcessInstanceID: "pi-readable",
			constants.FieldProcessInstanceHistory_TargetObjectID:    "target-readable",
			constants.FieldProcessInstanceHistory_TargetObjectType:  "GenericObject",
			constants.FieldProcessInstanceHistory_StepStatus:        constants.ApprovalStatusApproved,
			constants.FieldProcessInstanceHistory_ActorID:           "005-actor",
			constants.FieldProcessInstanceHistory_OriginalActorID:   "005-original",
			constants.FieldProcessInstanceHistory_Comments:          "hidden approval note",
		}
		return &models.QueryResult{TotalSize: 1, Done: true, Records: []models.SObject{selectProcessHistoryFields(row, req.SelectFields)}}, nil
	default:
		return nil, fmt.Errorf("unexpected Find schema %s", schema.APIName)
	}
}

func selectProcessHistoryFields(row models.SObject, fields []string) models.SObject {
	if len(fields) == 0 {
		return row
	}
	selected := make(models.SObject, len(fields))
	for _, field := range fields {
		if value, ok := row[field]; ok {
			selected[field] = value
		}
	}
	return selected
}

func queryCriteriaContainsINValues(criteria []models.QueryCriterion, field string, values ...string) bool {
	want := make(map[string]bool, len(values))
	for _, value := range values {
		want[value] = false
	}
	for _, criterion := range criteria {
		if !strings.EqualFold(criterion.Field, field) || !strings.EqualFold(criterion.Op, "IN") {
			continue
		}
		list, ok := criterion.Val.([]interface{})
		if !ok {
			continue
		}
		for _, item := range list {
			if value, ok := item.(string); ok {
				if _, exists := want[value]; exists {
					want[value] = true
				}
			}
		}
		for _, seen := range want {
			if !seen {
				return false
			}
		}
		return true
	}
	return false
}

func queryCriteriaContainsEqualValue(criteria []models.QueryCriterion, field string, value interface{}) bool {
	for _, criterion := range criteria {
		if strings.EqualFold(criterion.Field, field) && criterion.Op == "=" && criterion.Val == value {
			return true
		}
	}
	return false
}

type processInstanceHistorySubqueryMetadataRepo struct {
	ports.MetadataRepository

	schemas []*models.ObjectMetadata
}

func (r *processInstanceHistorySubqueryMetadataRepo) GetAllSchemas(ctx context.Context) ([]*models.ObjectMetadata, error) {
	return r.schemas, nil
}

func (r *processInstanceHistorySubqueryMetadataRepo) GetSchemaByAPIName(ctx context.Context, apiName string) (*models.ObjectMetadata, error) {
	for _, schema := range r.schemas {
		if strings.EqualFold(schema.APIName, apiName) {
			return schema, nil
		}
	}
	return nil, nil
}

func (r *processInstanceHistorySubqueryMetadataRepo) GetAllRelationships(ctx context.Context) ([]*models.Relationship, error) {
	return nil, nil
}

func (r *processInstanceHistorySubqueryMetadataRepo) GetSharingRules(ctx context.Context, objectAPIName string) ([]*models.SharingRule, error) {
	return nil, nil
}

type processInstanceHistorySubqueryPermissionRepo struct {
	ports.PermissionRepository

	denyHistoryComments bool
}

func (r *processInstanceHistorySubqueryPermissionRepo) GetAllRoles(ctx context.Context) ([]*models.UserRole, error) {
	return nil, nil
}

func (r *processInstanceHistorySubqueryPermissionRepo) GetUserGroupIDs(ctx context.Context, userID string) ([]string, error) {
	return nil, nil
}

func (r *processInstanceHistorySubqueryPermissionRepo) LoadEffectiveObjectPermission(ctx context.Context, user *models.UserSession, objectAPIName string) (*models.ObjectPermissions, error) {
	return &models.ObjectPermissions{PermissionsRead: true}, nil
}

func (r *processInstanceHistorySubqueryPermissionRepo) LoadEffectiveFieldPermission(ctx context.Context, user *models.UserSession, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error) {
	if strings.EqualFold(objectAPIName, constants.TableProcessInstanceHistory) &&
		strings.EqualFold(fieldAPIName, constants.FieldProcessInstanceHistory_Comments) &&
		r.denyHistoryComments {
		return &models.FieldPermissions{}, nil
	}
	return &models.FieldPermissions{PermissionsRead: true}, nil
}

func (r *processInstanceHistorySubqueryPermissionRepo) GetEffectiveSystemPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error) {
	return map[string]bool{}, nil
}

func processInstanceHistorySubqueryMetadataService(schemas ...*models.ObjectMetadata) *metadata.MetadataService {
	return metadata.NewMetadataService(&processInstanceHistorySubqueryMetadataRepo{schemas: schemas}, nil, nil)
}

func processInstanceHistorySubqueryProcessInstanceSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName:      constants.TableProcessInstance,
		TableType:    constants.TableTypeSystemData,
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
		},
	}
}

func processInstanceHistorySubqueryHistorySchema() *models.ObjectMetadata {
	childRelationshipName := "StepsAndWorkitems"
	return &models.ObjectMetadata{
		APIName:      constants.TableProcessInstanceHistory,
		TableType:    constants.TableTypeSystemData,
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:              "targetRecordRead",
			TargetObjectField: constants.FieldProcessInstanceHistory_TargetObjectType,
			TargetRecordField: constants.FieldProcessInstanceHistory_TargetObjectID,
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{
				APIName:               constants.FieldProcessInstanceHistory_ProcessInstanceID,
				Type:                  constants.FieldTypeReference,
				ReferenceTo:           []string{constants.TableProcessInstance},
				ChildRelationshipName: &childRelationshipName,
			},
			{APIName: constants.FieldProcessInstanceHistory_TargetObjectID, Type: constants.FieldTypeReference},
			{APIName: constants.FieldProcessInstanceHistory_TargetObjectType, Type: constants.FieldTypeString},
			{
				APIName:       constants.FieldProcessInstanceHistory_ActorID,
				Type:          constants.FieldTypeReference,
				ReferenceTo:   []string{constants.TableUser},
				IsPolymorphic: true,
			},
			{
				APIName:       constants.FieldProcessInstanceHistory_OriginalActorID,
				Type:          constants.FieldTypeReference,
				ReferenceTo:   []string{constants.TableUser},
				IsPolymorphic: true,
			},
			{APIName: constants.FieldProcessInstanceHistory_StepStatus, Type: constants.FieldTypeString},
			{APIName: constants.FieldProcessInstanceHistory_Comments, Type: constants.FieldTypeString},
			{APIName: constants.FieldProcessInstanceHistory_IsPending, Type: constants.FieldTypeBoolean},
		},
	}
}

func processInstanceHistorySubqueryUserSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName:      constants.TableUser,
		TableType:    constants.TableTypeSystemMetadata,
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldUsername, Type: constants.FieldTypeString},
			{APIName: constants.FieldFirstName, Type: constants.FieldTypeString},
			{APIName: constants.FieldLastName, Type: constants.FieldTypeString},
			{APIName: constants.FieldName, Type: constants.FieldTypeString, IsNameField: true, IsVirtual: true},
		},
	}
}

func processInstanceHistorySubqueryTargetSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName:      "GenericObject",
		TableType:    constants.TableTypeStandardObject,
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser}},
			{APIName: constants.FieldName, Type: constants.FieldTypeString, IsNameField: true},
		},
	}
}

func (*processInstanceHistorySubqueryPermissionRepo) GetManualShareRecordIDs(context.Context, string, string) ([]string, error) {
	return nil, nil
}
func (*processInstanceHistorySubqueryPermissionRepo) GetTeamMemberRecordIDs(context.Context, string, string) ([]string, error) {
	return nil, nil
}
func (*processInstanceHistorySubqueryPermissionRepo) GetGroupMemberIDs(context.Context, []string) ([]string, error) {
	return nil, nil
}
