package dialectsfcrm

import (
	"context"
	"encoding/json"
	"errors"
	"log"
	"net/http"

	"github.com/nexuscrm/dialect-sf-crm/wire"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/dialect"
	sharederrors "github.com/nexuscrm/shared/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
)

const approvalUnexpectedErrorMessage = "An unexpected error occurred"

type approvalProcessResult struct {
	ActorIds       []string                   `json:"actorIds"`
	EntityId       string                     `json:"entityId"`
	Errors         []string                   `json:"errors"`
	InstanceId     string                     `json:"instanceId"`
	InstanceStatus string                     `json:"instanceStatus"`
	NewWorkitemIds []string                   `json:"newWorkitemIds"`
	Success        bool                       `json:"success"`
	ActionFailures []approvalActionFailureDTO `json:"actionFailures,omitempty"`
}

type approvalActionFailureDTO struct {
	ActionName   string `json:"actionName"`
	ActionType   string `json:"actionType"`
	TargetObject string `json:"targetObject,omitempty"`
	TargetID     string `json:"targetId,omitempty"`
	Field        string `json:"field,omitempty"`
	Reason       string `json:"reason"`
}

type sfApprovalRequest struct {
	ActionType                string   `json:"actionType"`
	ContextActorId            string   `json:"contextActorId,omitempty"`
	ContextId                 string   `json:"contextId"`
	Comments                  string   `json:"comments,omitempty"`
	NextApproverIds           []string `json:"nextApproverIds,omitempty"`
	ProcessDefinitionNameOrId string   `json:"processDefinitionNameOrId,omitempty"`
	SkipEntryCriteria         bool     `json:"skipEntryCriteria,omitempty"`
}

type sfApprovalRequestWrapper struct {
	Requests []sfApprovalRequest `json:"requests"`
}

type legacyApprovalRequest struct {
	ActionType    string `json:"actionType"`
	ContextID     string `json:"contextId"`
	Comments      string `json:"comments"`
	ObjectAPIName string `json:"ObjectApiName"`
	RecordID      string `json:"RecordId"`
}

type workflowRule struct {
	ID           string           `json:"id"`
	Name         string           `json:"name"`
	SObject      string           `json:"sObject,omitempty"`
	Active       bool             `json:"active"`
	Actions      []workflowAction `json:"actions,omitempty"`
	Criteria     string           `json:"criteria,omitempty"`
	TriggerTypes []string         `json:"triggerTypes,omitempty"`
	URL          string           `json:"url,omitempty"`
}

type workflowAction struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Type string `json:"type"`
	URL  string `json:"url,omitempty"`
}

type approvalPendingViewDTO struct {
	WorkItemID          string `json:"workItemId"`
	ProcessInstanceID   string `json:"processInstanceId"`
	ProcessDefinitionID string `json:"processDefinitionId"`
	TargetObjectID      string `json:"targetObjectId"`
	ObjectAPIName       string `json:"objectApiName"`
	SubmittedByID       string `json:"submittedById"`
	SubmittedByName     string `json:"submittedByName"`
	SubmittedDate       string `json:"submittedDate"`
	ActorID             string `json:"actorId"`
	OriginalActorID     string `json:"originalActorId"`
	Comments            string `json:"comments,omitempty"`
	RecordName          string `json:"recordName,omitempty"`
}

type approvalProcessInstanceSummaryDTO struct {
	ID                  string                              `json:"id"`
	ProcessDefinitionID string                              `json:"processDefinitionId"`
	TargetObjectID      string                              `json:"targetObjectId"`
	Status              string                              `json:"status"`
	SubmittedByID       string                              `json:"submittedById"`
	SubmittedByName     string                              `json:"submittedByName"`
	CreatedDate         string                              `json:"createdDate"`
	CompletedDate       string                              `json:"completedDate,omitempty"`
	PendingWorkItems    []approvalPendingWorkItemSummaryDTO `json:"pendingWorkItems"`
}

type approvalPendingWorkItemSummaryDTO struct {
	ID                string `json:"id"`
	ActorID           string `json:"actorId"`
	ActorName         string `json:"actorName,omitempty"`
	OriginalActorID   string `json:"originalActorId,omitempty"`
	OriginalActorName string `json:"originalActorName,omitempty"`
}

type approvalStepDisplayMetadataDTO struct {
	ProcessNodeID    string `json:"processNodeId"`
	StepOrder        int    `json:"stepOrder,omitempty"`
	StepName         string `json:"stepName,omitempty"`
	StepLabel        string `json:"stepLabel,omitempty"`
	HasEntryCriteria bool   `json:"hasEntryCriteria"`
	IfCriteriaNotMet string `json:"ifCriteriaNotMet,omitempty"`
}

type approvalCloneRequest struct {
	ProcessDefinitionId string `json:"processDefinitionId"`
	NewDeveloperName    string `json:"newDeveloperName"`
	NewLabel            string `json:"newLabel"`
}

func ProcessApprovalsNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	approvals, resp := approvalContract(deps, "process approvals")
	if resp != nil {
		return resp, nil
	}

	var rawBody map[string]any
	if err := json.Unmarshal(req.Body, &rawBody); err != nil {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeJSONParserError, "Invalid JSON"), nil
	}

	sfRequests, wrapperRequest, resp := parseApprovalRequests(req.Body, rawBody, req.User)
	if resp != nil {
		return resp, nil
	}
	if len(sfRequests) == 0 {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, "At least one approval request is required"), nil
	}

	results := make([]approvalProcessResult, 0, len(sfRequests))
	for _, approvalReq := range sfRequests {
		if approvalReq.ActionType == "" {
			approvalReq.ActionType = "Submit"
		}
		switch approvalReq.ActionType {
		case "Submit":
			if approvalReq.ContextId == "" {
				results = append(results, failedApprovalProcessResult("Missing contextId for Submit action", ""))
				continue
			}
			objectName, _ := rawBody["ObjectApiName"].(string)
			workItem, err := approvals.Submit(ctx, contract.ApprovalSubmitRequest{
				ObjectAPIName:     objectName,
				RecordID:          approvalReq.ContextId,
				Comments:          approvalReq.Comments,
				SkipEntryCriteria: approvalReq.SkipEntryCriteria,
			}, req.User)
			if err != nil {
				if !wrapperRequest && len(sfRequests) == 1 {
					return respondApprovalSubmitError(err), nil
				}
				handleApprovalErrorInBatch(err, approvalReq.ContextId, &results)
				continue
			}
			instanceStatus := approvalRecordString(workItem, "InstanceStatus")
			if instanceStatus == "" {
				instanceStatus = constants.ApprovalStatusPending
			}
			results = append(results, buildApprovalProcessResult(workItem, approvalReq.ContextId, instanceStatus, req.User))
		case "Approve":
			if approvalReq.ContextId == "" {
				results = append(results, failedApprovalProcessResult("Missing contextId (WorkItemId) for Approve action", ""))
				continue
			}
			workItem, err := approvals.Approve(ctx, approvalReq.ContextId, approvalReq.Comments, req.User)
			if err != nil {
				handleApprovalErrorInBatch(err, approvalReq.ContextId, &results)
				continue
			}
			instanceStatus := approvalRecordString(workItem, "InstanceStatus")
			if instanceStatus == "" {
				instanceStatus = constants.ApprovalStatusApproved
			}
			results = append(results, buildApprovalProcessResult(workItem, approvalReq.ContextId, instanceStatus, req.User))
		case "Reject":
			if approvalReq.ContextId == "" {
				results = append(results, failedApprovalProcessResult("Missing contextId (WorkItemId) for Reject action", ""))
				continue
			}
			workItem, err := approvals.Reject(ctx, approvalReq.ContextId, approvalReq.Comments, req.User)
			if err != nil {
				handleApprovalErrorInBatch(err, approvalReq.ContextId, &results)
				continue
			}
			instanceStatus := approvalRecordString(workItem, "InstanceStatus")
			if instanceStatus == "" {
				instanceStatus = constants.ApprovalStatusRejected
			}
			results = append(results, buildApprovalProcessResult(workItem, approvalReq.ContextId, instanceStatus, req.User))
		case "Reassign":
			if approvalReq.ContextId == "" || len(approvalReq.NextApproverIds) == 0 {
				results = append(results, failedApprovalProcessResult("Missing contextId or nextApproverIds for Reassign action", ""))
				continue
			}
			if err := approvals.Reassign(ctx, approvalReq.ContextId, approvalReq.NextApproverIds[0], req.User); err != nil {
				handleApprovalErrorInBatch(err, approvalReq.ContextId, &results)
				continue
			}
			results = append(results, approvalProcessResult{
				Success:        true,
				ActorIds:       approvalReq.NextApproverIds[:1],
				InstanceId:     approvalReq.ContextId,
				InstanceStatus: constants.ApprovalStatusPending,
				NewWorkitemIds: []string{approvalReq.ContextId},
			})
		case "Removed":
			if approvalReq.ContextId == "" {
				results = append(results, failedApprovalProcessResult("Missing contextId (WorkItemId) for Removed action", ""))
				continue
			}
			workItem, err := approvals.Recall(ctx, approvalReq.ContextId, approvalReq.Comments, req.User)
			if err != nil {
				handleApprovalErrorInBatch(err, approvalReq.ContextId, &results)
				continue
			}
			results = append(results, buildApprovalProcessResult(workItem, approvalReq.ContextId, constants.ApprovalStatusRemoved, req.User))
		default:
			results = append(results, failedApprovalProcessResult("Unsupported actionType: "+approvalReq.ActionType, ""))
		}
	}

	return jsonResponse(http.StatusOK, results), nil
}

func ApprovalPendingNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	approvals, resp := approvalContract(deps, "list pending approvals")
	if resp != nil {
		return resp, nil
	}
	items, err := approvals.ListPendingApprovals(ctx, req.User)
	if err != nil {
		return approvalServiceErrorResponse(req, err), nil
	}
	return jsonResponse(http.StatusOK, approvalPendingViewsToDTO(items)), nil
}

func ApprovalLatestProcessInstanceNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	recordID := req.Params["recordId"]
	if recordID == "" {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, "recordId is required"), nil
	}
	approvals, resp := approvalContract(deps, "get latest approval process instance")
	if resp != nil {
		return resp, nil
	}
	summary, err := approvals.GetLatestProcessInstance(ctx, recordID, req.User)
	if err != nil {
		return approvalServiceErrorResponse(req, err), nil
	}
	return jsonResponse(http.StatusOK, approvalProcessInstanceSummaryToDTO(summary)), nil
}

func ApprovalStepDisplayMetadataNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	recordID := req.Params["recordId"]
	if recordID == "" {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, "recordId is required"), nil
	}
	approvals, resp := approvalContract(deps, "get approval step display metadata")
	if resp != nil {
		return resp, nil
	}
	facts, err := approvals.GetRecordApprovalStepDisplayMetadata(ctx, recordID, req.User)
	if err != nil {
		return approvalServiceErrorResponse(req, err), nil
	}
	return jsonResponse(http.StatusOK, approvalStepDisplayMetadataToDTO(facts)), nil
}

func WorkflowRulesNeutralHandler(context.Context, *dialect.Request, dialect.Deps) (*dialect.Response, error) {
	return jsonResponse(http.StatusOK, map[string]any{"rules": map[string][]workflowRule{}}), nil
}

func WorkflowRulesForObjectNeutralHandler(_ context.Context, req *dialect.Request, _ dialect.Deps) (*dialect.Response, error) {
	if req.Params["sObject"] == "" {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, "Missing sObject parameter"), nil
	}
	return jsonResponse(http.StatusOK, map[string]any{"rules": []workflowRule{}}), nil
}

func ApprovalNotificationFlowsNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	approvals, resp := approvalContract(deps, "list approval notification flows")
	if resp != nil {
		return resp, nil
	}
	flows, err := approvals.ListApprovalNotificationFlows(ctx)
	if err != nil {
		return approvalServiceErrorResponse(req, err), nil
	}
	actions := make([]models.InvocableFlow, 0, len(flows))
	for _, flow := range flows {
		actions = append(actions, invocableFlowToV63(req, flow, nil))
	}
	return jsonResponse(http.StatusOK, map[string]any{"actions": actions}), nil
}

func ApprovalCloneNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	approvals, resp := approvalContract(deps, "clone approval process")
	if resp != nil {
		return resp, nil
	}
	var body approvalCloneRequest
	if err := json.Unmarshal(req.Body, &body); err != nil {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeJSONParserError, "Invalid clone request: "+err.Error()), nil
	}
	if body.ProcessDefinitionId == "" {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, "ProcessDefinitionId is required"), nil
	}
	if body.NewDeveloperName == "" {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, "NewDeveloperName is required"), nil
	}
	if body.NewLabel == "" {
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, "NewLabel is required"), nil
	}
	created, err := approvals.Clone(ctx, body.ProcessDefinitionId, body.NewDeveloperName, body.NewLabel, req.User)
	if err != nil {
		return approvalServiceErrorResponse(req, err), nil
	}
	return jsonResponse(http.StatusOK, created), nil
}

func approvalContract(deps dialect.Deps, operation string) (contract.Approvals, *dialect.Response) {
	if deps.Contracts.Approvals != nil {
		return deps.Contracts.Approvals, nil
	}
	log.Printf("approval route unavailable during %s: missing Approvals contract", operation)
	return nil, &dialect.Response{Status: http.StatusInternalServerError, Body: wire.SFGenericInternalErrorResponse()}
}

func parseApprovalRequests(body []byte, raw map[string]any, user *models.UserSession) ([]sfApprovalRequest, bool, *dialect.Response) {
	if _, ok := raw["requests"]; ok {
		var wrapper sfApprovalRequestWrapper
		if err := json.Unmarshal(body, &wrapper); err != nil {
			return nil, true, approvalErrorResponse(http.StatusBadRequest, wire.SFCodeJSONParserError, "Invalid approval requests format")
		}
		return wrapper.Requests, true, nil
	}

	var legacy legacyApprovalRequest
	if err := json.Unmarshal(body, &legacy); err != nil {
		return nil, false, approvalErrorResponse(http.StatusBadRequest, wire.SFCodeJSONParserError, "Invalid approval request format")
	}
	contextID := legacy.ContextID
	if contextID == "" {
		contextID = legacy.RecordID
	}
	contextActorID := ""
	if user != nil {
		contextActorID = user.ID
	}
	return []sfApprovalRequest{{
		ActionType:     legacy.ActionType,
		ContextId:      contextID,
		ContextActorId: contextActorID,
		Comments:       legacy.Comments,
	}}, false, nil
}

func failedApprovalProcessResult(message string, entityID string) approvalProcessResult {
	return approvalProcessResult{
		Success:        false,
		EntityId:       entityID,
		InstanceStatus: "Error",
		ActorIds:       []string{},
		NewWorkitemIds: []string{},
		Errors:         []string{message},
	}
}

func buildApprovalProcessResult(workItem contract.Record, entityID, status string, user *models.UserSession) approvalProcessResult {
	result := approvalProcessResult{
		ActorIds:       []string{},
		EntityId:       entityID,
		Errors:         []string{},
		InstanceStatus: status,
		NewWorkitemIds: []string{},
		Success:        true,
	}
	if user != nil {
		result.ActorIds = []string{user.ID}
	}
	if workItem == nil {
		return result
	}
	if actorIDs, ok := approvalStringSlice(workItem["ActorIds"]); ok {
		result.ActorIds = actorIDs
	} else if user != nil {
		result.ActorIds = []string{user.ID}
	}
	hasWorkItemIDs := false
	if workItemIDs, ok := approvalStringSlice(workItem["NewWorkitemIds"]); ok {
		hasWorkItemIDs = true
		result.NewWorkitemIds = workItemIDs
	}
	if id := approvalRecordString(workItem, constants.FieldID); id != "" && !hasWorkItemIDs && len(result.NewWorkitemIds) == 0 {
		result.NewWorkitemIds = []string{id}
	}
	if instanceID := approvalRecordString(workItem, "ProcessInstanceId"); instanceID != "" {
		result.InstanceId = instanceID
	}
	if targetID := approvalRecordString(workItem, "TargetObjectId"); targetID != "" {
		result.EntityId = targetID
	}
	if failures := approvalActionFailuresToDTO(normalizeApprovalActionFailures(workItem["ActionFailures"])); len(failures) > 0 {
		result.ActionFailures = failures
	}
	return result
}

func approvalStringSlice(raw any) ([]string, bool) {
	switch value := raw.(type) {
	case []string:
		return append([]string(nil), value...), true
	case []any:
		out := make([]string, 0, len(value))
		for _, item := range value {
			if s, ok := item.(string); ok && s != "" {
				out = append(out, s)
			}
		}
		return out, true
	default:
		return nil, false
	}
}

func approvalRecordString(record contract.Record, key string) string {
	if record == nil {
		return ""
	}
	value, _ := record[key].(string)
	return value
}

func normalizeApprovalActionFailures(raw any) []contract.ApprovalProcessActionFailure {
	if raw == nil {
		return nil
	}
	if failures, ok := raw.([]contract.ApprovalProcessActionFailure); ok {
		return failures
	}
	bytes, err := json.Marshal(raw)
	if err != nil {
		return nil
	}
	var failures []contract.ApprovalProcessActionFailure
	if err := json.Unmarshal(bytes, &failures); err != nil {
		return nil
	}
	return failures
}

func handleApprovalErrorInBatch(err error, entityID string, results *[]approvalProcessResult) {
	message := err.Error()
	if sharederrors.IsInternal(err) {
		message = approvalUnexpectedErrorMessage
	}
	*results = append(*results, failedApprovalProcessResult(message, approvalSubmitErrorEntityID(err, entityID)))
}

func approvalSubmitErrorEntityID(err error, fallback string) string {
	var carrier interface{ SubmitTargetObjectID() string }
	if errors.As(err, &carrier) {
		if targetID := carrier.SubmitTargetObjectID(); targetID != "" {
			return targetID
		}
	}
	return fallback
}

func respondApprovalSubmitError(err error) *dialect.Response {
	if err == nil {
		return nil
	}
	if sharederrors.IsInternal(err) {
		return approvalInternalErrorResponse(http.StatusInternalServerError, "approval submit internal error", err)
	}
	if resp := approvalHTTPStatusErrorResponse(err); resp != nil {
		return resp
	}
	return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, err.Error())
}

func approvalServiceErrorResponse(req *dialect.Request, err error) *dialect.Response {
	if err == nil {
		return nil
	}
	if sharederrors.IsInternal(err) {
		return approvalInternalErrorResponse(http.StatusInternalServerError, "approval route internal error", err)
	}
	if resp := approvalHTTPStatusErrorResponse(err); resp != nil {
		return resp
	}
	if sfErr, status, ok := wire.SFErrorFromDomainForVersion(err, apiVersionOrDefault(req.Version)); ok {
		return &dialect.Response{Status: status, Body: []wire.SFError{sfErr}}
	}
	return approvalInternalErrorResponse(http.StatusInternalServerError, "approval route unclassified internal error", err)
}

func approvalHTTPStatusErrorResponse(err error) *dialect.Response {
	var statusErr httpStatusError
	if !errors.As(err, &statusErr) {
		return nil
	}
	status := statusErr.HTTPStatus()
	if status >= http.StatusInternalServerError && status <= 599 {
		return approvalInternalErrorResponse(status, "approval route status error", err)
	}
	switch status {
	case http.StatusBadRequest:
		return approvalErrorResponse(http.StatusBadRequest, wire.SFCodeInvalidRequest, err.Error())
	case http.StatusForbidden:
		return approvalErrorResponse(http.StatusForbidden, wire.SFCodeForbidden, err.Error())
	case http.StatusNotFound:
		return approvalErrorResponse(http.StatusNotFound, wire.SFCodeNotFound, err.Error())
	case http.StatusConflict:
		return approvalErrorResponse(http.StatusConflict, wire.SFCodeConflict, err.Error())
	default:
		return nil
	}
}

func approvalInternalErrorResponse(status int, context string, err error) *dialect.Response {
	if err != nil {
		log.Printf("%s: %v", context, err)
	} else {
		log.Print(context)
	}
	return &dialect.Response{Status: status, Body: wire.SFGenericInternalErrorResponse()}
}

func approvalErrorResponse(status int, code string, message string) *dialect.Response {
	return &dialect.Response{Status: status, Body: wire.SFErrorResponse(code, message)}
}

func approvalPendingViewsToDTO(items []contract.ApprovalPendingView) []approvalPendingViewDTO {
	out := make([]approvalPendingViewDTO, 0, len(items))
	for _, item := range items {
		out = append(out, approvalPendingViewDTO{
			WorkItemID:          item.WorkItemID,
			ProcessInstanceID:   item.ProcessInstanceID,
			ProcessDefinitionID: item.ProcessDefinitionID,
			TargetObjectID:      item.TargetObjectID,
			ObjectAPIName:       item.ObjectAPIName,
			SubmittedByID:       item.SubmittedByID,
			SubmittedByName:     item.SubmittedByName,
			SubmittedDate:       item.SubmittedDate,
			ActorID:             item.ActorID,
			OriginalActorID:     item.OriginalActorID,
			Comments:            item.Comments,
			RecordName:          item.RecordName,
		})
	}
	return out
}

func approvalProcessInstanceSummaryToDTO(summary *contract.ApprovalProcessInstanceSummary) *approvalProcessInstanceSummaryDTO {
	if summary == nil {
		return nil
	}
	out := &approvalProcessInstanceSummaryDTO{
		ID:                  summary.ID,
		ProcessDefinitionID: summary.ProcessDefinitionID,
		TargetObjectID:      summary.TargetObjectID,
		Status:              summary.Status,
		SubmittedByID:       summary.SubmittedByID,
		SubmittedByName:     summary.SubmittedByName,
		CreatedDate:         summary.CreatedDate,
		CompletedDate:       summary.CompletedDate,
		PendingWorkItems:    make([]approvalPendingWorkItemSummaryDTO, 0, len(summary.PendingWorkItems)),
	}
	for _, item := range summary.PendingWorkItems {
		out.PendingWorkItems = append(out.PendingWorkItems, approvalPendingWorkItemSummaryDTO{
			ID:                item.ID,
			ActorID:           item.ActorID,
			ActorName:         item.ActorName,
			OriginalActorID:   item.OriginalActorID,
			OriginalActorName: item.OriginalActorName,
		})
	}
	return out
}

func approvalStepDisplayMetadataToDTO(facts []contract.ApprovalStepDisplayMetadata) []approvalStepDisplayMetadataDTO {
	out := make([]approvalStepDisplayMetadataDTO, 0, len(facts))
	for _, fact := range facts {
		out = append(out, approvalStepDisplayMetadataDTO{
			ProcessNodeID:    fact.ProcessNodeID,
			StepOrder:        fact.StepOrder,
			StepName:         fact.StepName,
			StepLabel:        fact.StepLabel,
			HasEntryCriteria: fact.HasEntryCriteria,
			IfCriteriaNotMet: fact.IfCriteriaNotMet,
		})
	}
	return out
}

func approvalActionFailuresToDTO(failures []contract.ApprovalProcessActionFailure) []approvalActionFailureDTO {
	out := make([]approvalActionFailureDTO, 0, len(failures))
	for _, failure := range failures {
		out = append(out, approvalActionFailureDTO{
			ActionName:   failure.ActionName,
			ActionType:   failure.ActionType,
			TargetObject: failure.TargetObject,
			TargetID:     failure.TargetID,
			Field:        failure.Field,
			Reason:       failure.Reason,
		})
	}
	return out
}
