package automation

import (
	"context"
	"reflect"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/logging"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

func TestFlowResolveEmailRecipientsActionResolvesIdsBeforeSendEmail(t *testing.T) {
	query := &flowRecipientResolverQueryStub{
		users: map[string]models.SObject{
			"U-owner": {constants.FieldID: "U-owner", constants.FieldEmail: "owner@example.test"},
			"U-group": {constants.FieldID: "U-group", constants.FieldEmail: "group@example.test"},
		},
		groupMembers: map[string][]string{
			"G-ops": {"U-group"},
		},
		roleUsers: map[string][]models.SObject{
			"R-reviewers": {
				{constants.FieldID: "U-role", constants.FieldEmail: "role@example.test"},
			},
		},
	}
	executor := newFlowExecutorWithPlatformActionCalls(nil, nil, nil, nil, query, nil, logging.New(context.Background()))
	interview := &models.FlowInterview{
		ContextData: map[string]interface{}{
			"$Record": models.SObject{constants.FieldOwnerID: "U-owner"},
		},
	}
	action := &models.FlowActionCall{
		Name:       "Resolve_Recipients",
		ActionName: "Resolve_Recipients",
		ActionType: flowResolveEmailRecipientsActionType,
		InputParameters: []models.FlowActionParameter{
			{Name: "emails", Value: []interface{}{"direct@example.test"}},
			{Name: "userIds", Value: map[string]interface{}{"elementReference": "$Record.OwnerId"}},
			{Name: "groupIds", Value: []interface{}{"G-ops"}},
			{Name: "roleIds", Value: "R-reviewers"},
		},
		OutputParameters: []models.FlowActionParameter{
			{Name: "emails", Value: "ResolvedEmails"},
			{Name: "diagnostics", Value: "RecipientDiagnostics"},
			{Name: "resolvedCount", Value: "RecipientResolvedCount"},
			{Name: "invalidCount", Value: "RecipientInvalidCount"},
		},
		Connector: &models.FlowConnector{TargetReference: "next"},
	}

	next, err := executor.executeActionCall(context.Background(), interview, &models.FlowMetadata{}, action, &models.UserSession{ID: "U-requester"})
	if err != nil {
		t.Fatalf("executeActionCall(resolveEmailRecipients): %v", err)
	}
	if next != "next" {
		t.Fatalf("next = %q; want next", next)
	}
	wantEmails := []string{"direct@example.test", "owner@example.test", "group@example.test", "role@example.test"}
	if got := interview.ContextData["ResolvedEmails"]; !reflect.DeepEqual(got, wantEmails) {
		t.Fatalf("ResolvedEmails = %#v; want %#v", got, wantEmails)
	}
	if got := interview.ContextData["RecipientResolvedCount"]; got != 4 {
		t.Fatalf("RecipientResolvedCount = %#v; want 4", got)
	}
	if got := interview.ContextData["RecipientInvalidCount"]; got != 0 {
		t.Fatalf("RecipientInvalidCount = %#v; want 0", got)
	}
	if diagnostics := interview.ContextData["RecipientDiagnostics"].([]interface{}); len(diagnostics) != 0 {
		t.Fatalf("RecipientDiagnostics = %#v; want empty", diagnostics)
	}
	assertFlowRecipientQueryCall(t, query.calls, constants.TableUser, "Id = 'U-owner'")
	assertFlowRecipientQueryCall(t, query.calls, constants.TableGroupMembershipClosure, "GroupId = 'G-ops' AND MemberType = 'User'")
	assertFlowRecipientQueryCall(t, query.calls, constants.TableUser, "UserRoleId = 'R-reviewers'")
}

func TestFlowResolveEmailRecipientsActionDiagnosticsForInvalidSources(t *testing.T) {
	query := &flowRecipientResolverQueryStub{
		users: map[string]models.SObject{
			"U-no-email": {constants.FieldID: "U-no-email"},
		},
	}
	executor := newFlowExecutorWithPlatformActionCalls(nil, nil, nil, nil, query, nil, logging.New(context.Background()))
	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	action := &models.FlowActionCall{
		Name:       "Resolve_Recipients",
		ActionName: "Resolve_Recipients",
		ActionType: flowResolveEmailRecipientsActionType,
		InputParameters: []models.FlowActionParameter{
			{Name: "emails", Value: []interface{}{"not-an-email"}},
			{Name: "userIds", Value: []interface{}{"U-no-email", "U-missing"}},
		},
		OutputParameters: []models.FlowActionParameter{
			{Name: "emails", Value: "ResolvedEmails"},
			{Name: "diagnostics", Value: "RecipientDiagnostics"},
			{Name: "resolvedCount", Value: "RecipientResolvedCount"},
			{Name: "invalidCount", Value: "RecipientInvalidCount"},
		},
	}

	_, err := executor.executeActionCall(context.Background(), interview, &models.FlowMetadata{}, action, &models.UserSession{ID: "U-requester"})
	if err != nil {
		t.Fatalf("executeActionCall(resolveEmailRecipients): %v", err)
	}
	if got := interview.ContextData["ResolvedEmails"]; !reflect.DeepEqual(got, []string{}) {
		t.Fatalf("ResolvedEmails = %#v; want empty email slice", got)
	}
	if got := interview.ContextData["RecipientResolvedCount"]; got != 0 {
		t.Fatalf("RecipientResolvedCount = %#v; want 0", got)
	}
	if got := interview.ContextData["RecipientInvalidCount"]; got != 3 {
		t.Fatalf("RecipientInvalidCount = %#v; want 3", got)
	}
	diagnostics := interview.ContextData["RecipientDiagnostics"].([]interface{})
	if len(diagnostics) != 3 {
		t.Fatalf("RecipientDiagnostics len = %d; want 3 (%#v)", len(diagnostics), diagnostics)
	}
}

func TestFlowResolveEmailRecipientsActionStripsElevatedReadContext(t *testing.T) {
	query := &flowRecipientResolverQueryStub{
		onQuery: func(ctx context.Context, objectName string, filterExpr string, user *models.UserSession, sortField string, sortDir string, limit int) (*models.QueryResult, error) {
			if got := constants.GetSecurityContext(ctx); got != constants.UserContext {
				return &models.QueryResult{Records: []models.SObject{{
					constants.FieldID:    "leaked-security-context",
					constants.FieldEmail: "leaked-security-context@example.test",
				}}}, nil
			}
			if constants.HasStandardActionInternalAccessScopes(ctx) ||
				constants.IsScopedInternalAccess(ctx, objectName, constants.PermRead) ||
				constants.ScopedInternalAccessBypassesFLS(ctx, objectName, constants.PermRead) {
				return &models.QueryResult{Records: []models.SObject{{
					constants.FieldID:    "leaked-scoped-access",
					constants.FieldEmail: "leaked-scoped-access@example.test",
				}}}, nil
			}
			return &models.QueryResult{}, nil
		},
	}
	executor := newFlowExecutorWithPlatformActionCalls(nil, nil, nil, nil, query, nil, logging.New(context.Background()))
	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	action := &models.FlowActionCall{
		Name:       "Resolve_Recipients",
		ActionName: "Resolve_Recipients",
		ActionType: flowResolveEmailRecipientsActionType,
		InputParameters: []models.FlowActionParameter{
			{Name: "userIds", Value: "U-hidden"},
			{Name: "groupIds", Value: "G-hidden"},
			{Name: "roleIds", Value: "R-hidden"},
		},
		OutputParameters: []models.FlowActionParameter{
			{Name: "emails", Value: "ResolvedEmails"},
			{Name: "diagnostics", Value: "RecipientDiagnostics"},
			{Name: "invalidCount", Value: "RecipientInvalidCount"},
		},
	}
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "record-triggered flow execution")
	ctx = constants.WithStandardActionInternalAccessScopes(ctx, "resolver-test", []constants.StandardActionInternalAccessScope{
		{StandardActionID: "resolver-test", ObjectAPIName: constants.TableUser, Operation: constants.PermRead},
		{StandardActionID: "resolver-test", ObjectAPIName: constants.TableGroupMembershipClosure, Operation: constants.PermRead},
	})

	_, err := executor.executeActionCall(ctx, interview, &models.FlowMetadata{}, action, &models.UserSession{ID: "U-requester"})
	if err != nil {
		t.Fatalf("executeActionCall(resolveEmailRecipients): %v", err)
	}
	if got := interview.ContextData["ResolvedEmails"]; !reflect.DeepEqual(got, []string{}) {
		t.Fatalf("ResolvedEmails = %#v; want empty; elevated/scoped read context must not leak recipient emails", got)
	}
	if got := interview.ContextData["RecipientInvalidCount"]; got != 3 {
		t.Fatalf("RecipientInvalidCount = %#v; want 3 diagnostics for hidden user/group/role", got)
	}
	if len(query.calls) != 3 {
		t.Fatalf("QueryWithFilter calls = %d; want 3 user/group/role read attempts through user context", len(query.calls))
	}
}

func TestFlowResolveEmailRecipientsActionRejectsLegacyWorkflowAlertRecipientObjects(t *testing.T) {
	query := &flowRecipientResolverQueryStub{}
	executor := newFlowExecutorWithPlatformActionCalls(nil, nil, nil, nil, query, nil, logging.New(context.Background()))
	action := &models.FlowActionCall{
		Name:       "Resolve_Recipients",
		ActionName: "Resolve_Recipients",
		ActionType: flowResolveEmailRecipientsActionType,
		InputParameters: []models.FlowActionParameter{
			{Name: "emails", Value: []interface{}{
				map[string]interface{}{"type": "user", "recipient": "U-legacy"},
			}},
		},
	}

	_, err := executor.executeActionCall(context.Background(), &models.FlowInterview{ContextData: map[string]interface{}{}}, &models.FlowMetadata{}, action, &models.UserSession{ID: "U-requester"})
	if err == nil {
		t.Fatal("executeActionCall(resolveEmailRecipients) succeeded; want structured recipient rejection")
	}
	if !strings.Contains(err.Error(), "does not accept structured recipient objects") {
		t.Fatalf("error = %q; want structured recipient rejection", err.Error())
	}
	if len(query.calls) != 0 {
		t.Fatalf("QueryWithFilter calls = %#v; want none before legacy shape rejection", query.calls)
	}
}

type flowRecipientResolverQueryCall struct {
	objectName string
	filterExpr string
	sortField  string
	sortDir    string
	limit      int
}

type flowRecipientResolverQueryStub struct {
	users        map[string]models.SObject
	groupMembers map[string][]string
	roleUsers    map[string][]models.SObject
	calls        []flowRecipientResolverQueryCall
	onQuery      func(ctx context.Context, objectName string, filterExpr string, user *models.UserSession, sortField string, sortDir string, limit int) (*models.QueryResult, error)
}

func (s *flowRecipientResolverQueryStub) QueryWithFilter(ctx context.Context, objectName string, filterExpr string, user *models.UserSession, sortField string, sortDir string, limit int) (*models.QueryResult, error) {
	s.calls = append(s.calls, flowRecipientResolverQueryCall{
		objectName: objectName,
		filterExpr: filterExpr,
		sortField:  sortField,
		sortDir:    sortDir,
		limit:      limit,
	})
	if s.onQuery != nil {
		return s.onQuery(ctx, objectName, filterExpr, user, sortField, sortDir, limit)
	}
	switch objectName {
	case constants.TableUser:
		if roleID := filterQuotedValue(filterExpr, constants.FieldUserRoleID); roleID != "" {
			return &models.QueryResult{Records: append([]models.SObject(nil), s.roleUsers[roleID]...)}, nil
		}
		if userID := filterQuotedValue(filterExpr, constants.FieldID); userID != "" {
			if record, ok := s.users[userID]; ok {
				return &models.QueryResult{Records: []models.SObject{record}}, nil
			}
		}
		return &models.QueryResult{}, nil
	case constants.TableGroupMembershipClosure:
		groupID := filterQuotedValue(filterExpr, constants.FieldSysGroupMembershipClosure_GroupID)
		members := s.groupMembers[groupID]
		records := make([]models.SObject, 0, len(members))
		for _, memberID := range members {
			records = append(records, models.SObject{constants.FieldSysGroupMembershipClosure_MemberID: memberID})
		}
		return &models.QueryResult{Records: records}, nil
	default:
		return &models.QueryResult{}, nil
	}
}

func (s *flowRecipientResolverQueryStub) QueryWithSpec(context.Context, queryspec.QuerySpec, *models.UserSession) (*models.QueryResult, error) {
	return nil, nil
}
func (s *flowRecipientResolverQueryStub) RunAnalytics(context.Context, models.AnalyticsQuery, *models.UserSession) (interface{}, error) {
	return nil, nil
}
func (s *flowRecipientResolverQueryStub) SearchSingleObject(context.Context, string, string, []string, []string, int, int, string, string, string, *models.UserSession) ([]models.SObject, error) {
	return nil, nil
}
func (s *flowRecipientResolverQueryStub) SearchSuggestions(context.Context, string, string, []string, []string, int, *models.UserSession) ([]models.SObject, error) {
	return nil, nil
}
func (s *flowRecipientResolverQueryStub) GlobalSearch(context.Context, string, *models.UserSession) ([]models.SearchResult, error) {
	return nil, nil
}
func (s *flowRecipientResolverQueryStub) Calculate(context.Context, string, models.SObject, *models.UserSession) (models.SObject, error) {
	return nil, nil
}
func (s *flowRecipientResolverQueryStub) ExecuteRawSQL(context.Context, string, []interface{}, *models.UserSession) ([]models.SObject, error) {
	return nil, nil
}

func filterQuotedValue(filterExpr, fieldName string) string {
	prefix := fieldName + " = '"
	idx := strings.Index(filterExpr, prefix)
	if idx < 0 {
		return ""
	}
	remainder := filterExpr[idx+len(prefix):]
	end := strings.Index(remainder, "'")
	if end < 0 {
		return ""
	}
	return remainder[:end]
}

func assertFlowRecipientQueryCall(t *testing.T, calls []flowRecipientResolverQueryCall, objectName string, filterExpr string) {
	t.Helper()
	for _, call := range calls {
		if call.objectName == objectName && call.filterExpr == filterExpr {
			return
		}
	}
	t.Fatalf("QueryWithFilter calls = %#v; want %s %q", calls, objectName, filterExpr)
}
