package automation

import (
	"context"
	"fmt"
	"strings"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/utils"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

const (
	flowEmailRecipientSourceEmail   = "email"
	flowEmailRecipientSourceUserID  = "userId"
	flowEmailRecipientSourceGroupID = "groupId"
	flowEmailRecipientSourceRoleID  = "roleId"
)

type flowEmailRecipientSource struct {
	SourceType string
	Value      string
	ParamName  string
}

type flowEmailRecipientDiagnostic struct {
	SourceType  string
	SourceValue string
	Message     string
}

type flowEmailRecipientResolution struct {
	Emails      []string
	Diagnostics []flowEmailRecipientDiagnostic
	seenEmails  map[string]struct{}
}

func (e *FlowExecutor) executeResolveEmailRecipientsAction(ctx context.Context, interview *models.FlowInterview, action *models.FlowActionCall, user *models.UserSession) (string, error) {
	fault := func(err error) (string, error) {
		if action.FaultConnector != nil {
			return e.routeFault(ctx, interview, action.FaultConnector.TargetReference, err)
		}
		return "", err
	}

	sources, err := e.collectFlowEmailRecipientSources(ctx, interview, action, user)
	if err != nil {
		return fault(err)
	}
	result := e.resolveFlowEmailRecipientSources(ctx, sources, user)
	assignActionCallOutputs(interview, action, flowEmailRecipientOutputValues(result))

	if action.Connector != nil {
		return action.Connector.TargetReference, nil
	}
	return "", nil
}

func (e *FlowExecutor) collectFlowEmailRecipientSources(ctx context.Context, interview *models.FlowInterview, action *models.FlowActionCall, user *models.UserSession) ([]flowEmailRecipientSource, error) {
	var sources []flowEmailRecipientSource
	for _, param := range action.InputParameters {
		name := strings.TrimSpace(param.Name)
		if name == "" {
			continue
		}
		sourceType, ok := flowEmailRecipientInputSourceType(name)
		if !ok {
			return nil, apperrors.NewValidationError(name, "resolveEmailRecipients supports only emails, userIds, groupIds, and roleIds inputs")
		}
		resolved, err := e.deepResolveFlowResource(ctx, interview, user, param.Value)
		if err != nil {
			return nil, err
		}
		value := unwrapCanonicalSendEmailTypedValue(resolved)
		values, err := collectFlowEmailRecipientInputValues(name, value)
		if err != nil {
			return nil, err
		}
		for _, value := range values {
			sources = append(sources, flowEmailRecipientSource{
				SourceType: sourceType,
				Value:      value,
				ParamName:  name,
			})
		}
	}
	return sources, nil
}

func flowEmailRecipientInputSourceType(name string) (string, bool) {
	switch strings.ToLower(strings.TrimSpace(name)) {
	case "email", "emails", "emailaddress", "emailaddresses":
		return flowEmailRecipientSourceEmail, true
	case "userid", "userids", "lookupuserid", "lookupuserids", "userlookupid", "userlookupids":
		return flowEmailRecipientSourceUserID, true
	case "groupid", "groupids":
		return flowEmailRecipientSourceGroupID, true
	case "roleid", "roleids":
		return flowEmailRecipientSourceRoleID, true
	default:
		return "", false
	}
}

func collectFlowEmailRecipientInputValues(fieldName string, value interface{}) ([]string, error) {
	switch v := value.(type) {
	case nil:
		return []string{""}, nil
	case string:
		return []string{strings.TrimSpace(v)}, nil
	case []string:
		values := make([]string, 0, len(v))
		for _, item := range v {
			values = append(values, strings.TrimSpace(item))
		}
		return values, nil
	case []interface{}:
		var values []string
		for _, item := range v {
			nested, err := collectFlowEmailRecipientInputValues(fieldName, unwrapCanonicalSendEmailTypedValue(item))
			if err != nil {
				return nil, err
			}
			values = append(values, nested...)
		}
		return values, nil
	case map[string]interface{}:
		return nil, apperrors.NewValidationError(fieldName, "resolveEmailRecipients does not accept structured recipient objects; pass emails, userIds, groupIds, or roleIds explicitly")
	default:
		return nil, apperrors.NewValidationError(fieldName, fmt.Sprintf("%s must be a string or collection of strings", fieldName))
	}
}

func (e *FlowExecutor) resolveFlowEmailRecipientSources(ctx context.Context, sources []flowEmailRecipientSource, user *models.UserSession) flowEmailRecipientResolution {
	readCtx := flowEmailRecipientReadContext(ctx)
	result := flowEmailRecipientResolution{seenEmails: map[string]struct{}{}}
	for _, source := range sources {
		switch source.SourceType {
		case flowEmailRecipientSourceEmail:
			result.addEmail(source, source.Value)
		case flowEmailRecipientSourceUserID:
			e.resolveFlowEmailRecipientUserID(readCtx, source, user, &result)
		case flowEmailRecipientSourceGroupID:
			e.resolveFlowEmailRecipientGroupID(readCtx, source, user, &result)
		case flowEmailRecipientSourceRoleID:
			e.resolveFlowEmailRecipientRoleID(readCtx, source, user, &result)
		default:
			result.addDiagnostic(source, "unsupported recipient source type")
		}
	}
	return result
}

func flowEmailRecipientReadContext(ctx context.Context) context.Context {
	ctx = constants.WithoutStandardActionInternalAccessScopes(ctx)
	return constants.WithSecurityContext(ctx, constants.UserContext, "resolveEmailRecipients recipient lookup")
}

func (e *FlowExecutor) resolveFlowEmailRecipientUserID(ctx context.Context, source flowEmailRecipientSource, user *models.UserSession, result *flowEmailRecipientResolution) {
	userID := strings.TrimSpace(source.Value)
	if userID == "" {
		result.addDiagnostic(source, "userId is empty")
		return
	}
	record, message := e.queryFlowEmailRecipientUser(ctx, userID, user)
	if message != "" {
		result.addDiagnostic(source, message)
		return
	}
	email := utils.GetConfigString(record, constants.FieldEmail)
	if strings.TrimSpace(email) == "" {
		result.addDiagnostic(source, "user has no email address or email is not visible")
		return
	}
	result.addEmail(source, email)
}

func (e *FlowExecutor) resolveFlowEmailRecipientGroupID(ctx context.Context, source flowEmailRecipientSource, user *models.UserSession, result *flowEmailRecipientResolution) {
	groupID := strings.TrimSpace(source.Value)
	if groupID == "" {
		result.addDiagnostic(source, "groupId is empty")
		return
	}
	if e.query == nil {
		result.addDiagnostic(source, "query service is unavailable for group recipient resolution")
		return
	}
	filter := fmt.Sprintf(
		"GroupId = '%s' AND MemberType = '%s'",
		escapeApprovalFilterValue(groupID),
		escapeApprovalFilterValue(constants.GroupMembershipClosureMemberTypeUser),
	)
	members, err := e.query.QueryWithFilter(ctx, constants.TableGroupMembershipClosure, filter, user, constants.FieldSysGroupMembershipClosure_MemberID, "ASC", 200)
	if err != nil {
		result.addDiagnostic(source, "group member lookup failed")
		return
	}
	if members == nil || len(members.Records) == 0 {
		result.addDiagnostic(source, "group has no user members or is not visible")
		return
	}
	for _, member := range members.Records {
		memberID := strings.TrimSpace(utils.GetConfigString(member, constants.FieldSysGroupMembershipClosure_MemberID))
		if memberID == "" {
			result.addDiagnostic(source, "group member row has no user id")
			continue
		}
		record, message := e.queryFlowEmailRecipientUser(ctx, memberID, user)
		if message != "" {
			result.addDiagnostic(source, "group member "+message)
			continue
		}
		result.addEmail(source, utils.GetConfigString(record, constants.FieldEmail))
	}
}

func (e *FlowExecutor) resolveFlowEmailRecipientRoleID(ctx context.Context, source flowEmailRecipientSource, user *models.UserSession, result *flowEmailRecipientResolution) {
	roleID := strings.TrimSpace(source.Value)
	if roleID == "" {
		result.addDiagnostic(source, "roleId is empty")
		return
	}
	if e.query == nil {
		result.addDiagnostic(source, "query service is unavailable for role recipient resolution")
		return
	}
	filter := fmt.Sprintf("%s = '%s'", constants.FieldUserRoleID, escapeApprovalFilterValue(roleID))
	users, err := e.query.QueryWithFilter(ctx, constants.TableUser, filter, user, constants.FieldID, "ASC", 200)
	if err != nil {
		result.addDiagnostic(source, "role user lookup failed")
		return
	}
	if users == nil || len(users.Records) == 0 {
		result.addDiagnostic(source, "role has no users or is not visible")
		return
	}
	for _, record := range users.Records {
		email := utils.GetConfigString(record, constants.FieldEmail)
		if strings.TrimSpace(email) == "" {
			result.addDiagnostic(source, "role user has no email address or email is not visible")
			continue
		}
		result.addEmail(source, email)
	}
}

func (e *FlowExecutor) queryFlowEmailRecipientUser(ctx context.Context, userID string, user *models.UserSession) (models.SObject, string) {
	if e.query == nil {
		return nil, "query service is unavailable for user recipient resolution"
	}
	filter := fmt.Sprintf("Id = '%s'", escapeApprovalFilterValue(userID))
	users, err := e.query.QueryWithFilter(ctx, constants.TableUser, filter, user, "", "", 1)
	if err != nil {
		return nil, "user lookup failed"
	}
	if users == nil || len(users.Records) == 0 {
		return nil, "user not found or not visible"
	}
	return users.Records[0], ""
}

func (r *flowEmailRecipientResolution) addEmail(source flowEmailRecipientSource, email string) {
	address, err := validateCanonicalSendEmailAddress(source.ParamName, email)
	if err != nil {
		r.addDiagnostic(source, err.Error())
		return
	}
	key := strings.ToLower(address)
	if _, exists := r.seenEmails[key]; exists {
		return
	}
	r.seenEmails[key] = struct{}{}
	r.Emails = append(r.Emails, address)
}

func (r *flowEmailRecipientResolution) addDiagnostic(source flowEmailRecipientSource, message string) {
	message = strings.TrimSpace(message)
	if message == "" {
		message = "recipient source could not be resolved"
	}
	r.Diagnostics = append(r.Diagnostics, flowEmailRecipientDiagnostic{
		SourceType:  source.SourceType,
		SourceValue: strings.TrimSpace(source.Value),
		Message:     message,
	})
}

func flowEmailRecipientOutputValues(result flowEmailRecipientResolution) map[string]interface{} {
	emails := make([]string, 0, len(result.Emails))
	emails = append(emails, result.Emails...)
	diagnostics := make([]interface{}, 0, len(result.Diagnostics))
	for _, diagnostic := range result.Diagnostics {
		row := map[string]interface{}{
			"sourceType": diagnostic.SourceType,
			"status":     "invalid",
			"message":    diagnostic.Message,
		}
		if diagnostic.SourceValue != "" {
			row["sourceValue"] = diagnostic.SourceValue
		}
		diagnostics = append(diagnostics, row)
	}
	return map[string]interface{}{
		flowResolveEmailRecipientsEmailsOutput:      emails,
		flowResolveEmailRecipientsDiagnosticsOutput: diagnostics,
		flowResolveEmailRecipientsResolvedCount:     len(result.Emails),
		flowResolveEmailRecipientsInvalidCount:      len(result.Diagnostics),
	}
}
