package rest

import (
	"context"
	"encoding/xml"
	"fmt"
	"strings"

	"github.com/nexuscrm/backend/internal/domain/ports"
	appErrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

type approvalProcessPersistence interface {
	RunInTransaction(ctx context.Context, fn func(ctx context.Context) error) error
	Insert(ctx context.Context, objectName string, data models.SObject, currentUser *models.UserSession) (models.SObject, error)
	Update(ctx context.Context, objectName string, id string, updates models.SObject, currentUser *models.UserSession) error
	Delete(ctx context.Context, objectName string, id string, currentUser *models.UserSession) error
	PhysicalDeleteRaw(ctx context.Context, objectName string, id string) error
	RetrieveByField(ctx context.Context, objectName string, fieldName string, fieldValue string, user *models.UserSession) (models.SObject, error)
}

func approvalProcessMetadataPayload(objectName string, data map[string]interface{}) (string, bool) {
	if !strings.EqualFold(objectName, constants.TableProcessDefinition) {
		return "", false
	}

	rawMetadata, ok := data[constants.FieldProcessDefinition_Metadata].(string)
	if !ok || strings.TrimSpace(rawMetadata) == "" {
		return "", false
	}

	if _, err := parseApprovalProcessXML(rawMetadata); err != nil {
		return "", false
	}

	return rawMetadata, true
}

func parseApprovalProcessXML(raw string) (*ApprovalProcessXML, error) {
	var xmlData ApprovalProcessXML
	if err := xml.Unmarshal([]byte(strings.TrimSpace(raw)), &xmlData); err != nil {
		return nil, err
	}
	return &xmlData, nil
}

func normalizeApprovalSyncApproverType(raw string) string {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "relateduser", "relateduserfield":
		return "relatedUserField"
	case "user":
		return "user"
	case "queue":
		return "queue"
	case "role":
		return "role"
	case "hierarchy":
		return "hierarchy"
	default:
		return strings.TrimSpace(raw)
	}
}

func buildApprovalSyncProcessNodeRecord(step ApprovalStepXML, processDefinitionID string, order int) models.SObject {
	record := models.SObject{
		"Name":                step.Name,
		"DeveloperName":       step.Name,
		"Description":         step.Description,
		"ProcessDefinitionId": processDefinitionID,
		"AllowComments":       true,
		"RequireComments":     false,
		"StepOrder":           order,
		"RejectBehavior":      "FinalRejection",
	}
	if step.AllowComments != nil {
		record["AllowComments"] = *step.AllowComments
	}
	if step.RequireComments != nil {
		record["RequireComments"] = *step.RequireComments
	}
	if step.RejectBehavior != nil {
		record["RejectBehavior"] = *step.RejectBehavior
	}
	return record
}

func buildApprovalSyncActionRecord(action ApprovalActionXML, processDefinitionID string, actionEvent string, order int) models.SObject {
	record := models.SObject{
		"Name":                action.Name,
		"ProcessDefinitionId": processDefinitionID,
		"ActionType":          action.Type,
		"ActionEvent":         actionEvent,
		"Order":               order,
	}
	if action.FieldApiName != "" {
		record["FieldApiName"] = action.FieldApiName
	}
	if action.FieldValue != "" {
		record["FieldValue"] = action.FieldValue
	}
	if action.EmailTemplateId != "" {
		record["EmailTemplateId"] = action.EmailTemplateId
	}
	if action.ApiName != "" {
		record["ApiName"] = action.ApiName
	}
	return record
}

func insertApprovalActionGroup(
	ctx context.Context,
	persistence approvalProcessPersistence,
	processDefinitionID string,
	processNodeID string,
	actionEvent string,
	actions *ApprovalActionsXML,
	user *models.UserSession,
) error {
	if actions == nil {
		return nil
	}

	for order, action := range actions.Actions {
		actionRecord := buildApprovalSyncActionRecord(action, processDefinitionID, actionEvent, order)
		if processNodeID != "" {
			actionRecord["ProcessNodeId"] = processNodeID
		}
		if _, err := persistence.Insert(ctx, constants.TableProcessAction, actionRecord, user); err != nil {
			return appErrors.NewInternalError(fmt.Sprintf("failed to insert action %s", action.Name), err)
		}
	}

	return nil
}

func syncApprovalProcessChildren(
	ctx context.Context,
	persistence approvalProcessPersistence,
	querySvc ports.QueryService,
	processDefinitionID string,
	rawApprovalXML string,
	fileName string,
	user *models.UserSession,
	reportFailure func(ComponentFailure),
) error {
	xmlData, err := parseApprovalProcessXML(rawApprovalXML)
	if err != nil {
		return appErrors.NewInternalError("invalid approval-process metadata", err)
	}

	physicalDeleteExistingApprovalChildren := func(objectName string) error {
		filter := fmt.Sprintf("ProcessDefinitionId == '%s'", processDefinitionID)
		res, queryErr := querySvc.QueryWithFilter(ctx, objectName, filter, user, "", "", 1000)
		if queryErr != nil {
			return nil
		}
		for _, rec := range res.Records {
			recordID, _ := rec[constants.FieldID].(string)
			if recordID == "" {
				continue
			}
			if err := persistence.PhysicalDeleteRaw(ctx, objectName, recordID); err != nil {
				return appErrors.NewInternalError(fmt.Sprintf("failed to delete old %s", strings.ToLower(objectName)), err)
			}
		}
		return nil
	}

	if err := physicalDeleteExistingApprovalChildren(constants.TableProcessAction); err != nil {
		return err
	}
	if err := physicalDeleteExistingApprovalChildren(constants.TableProcessNode); err != nil {
		return err
	}

	for index, step := range xmlData.Steps {
		stepRecord := buildApprovalSyncProcessNodeRecord(step, processDefinitionID, index+1)

		if step.AssignedApprover != nil {
			for _, approver := range step.AssignedApprover.Approvers {
				approverType := normalizeApprovalSyncApproverType(approver.Type)
				switch approverType {
				case "user":
					stepRecord["ApproverType"] = approverType
					userRecord, lookupErr := persistence.RetrieveByField(ctx, constants.TableUser, constants.FieldUsername, approver.Name, user)
					if lookupErr == nil && userRecord != nil {
						stepRecord["ApproverUserId"], _ = userRecord[constants.FieldID].(string)
					} else {
						failure := ComponentFailure{
							Problem:       fmt.Sprintf("Could not resolve approver user '%s' — stored without UserId", approver.Name),
							FileName:      fileName,
							ComponentType: "ApprovalProcess",
						}
						if reportFailure != nil {
							reportFailure(failure)
						} else {
							log.Warn().Str("approver", approver.Name).Str("processDefinitionId", processDefinitionID).Msg("approval-process approver could not be resolved")
						}
					}
				case "relatedUserField":
					stepRecord["ApproverType"] = approverType
					stepRecord["ApproverRelatedField"] = approver.Name
				case "queue", "role", "hierarchy":
					stepRecord["ApproverType"] = approverType
				}
				break
			}
		}

		insertedStep, err := persistence.Insert(ctx, constants.TableProcessNode, stepRecord, user)
		if err != nil {
			return appErrors.NewInternalError(fmt.Sprintf("failed to insert step %s", step.Name), err)
		}
		stepID, _ := insertedStep[constants.FieldID].(string)

		if err := insertApprovalActionGroup(ctx, persistence, processDefinitionID, stepID, "StepApproval", step.StepApprovalActions, user); err != nil {
			return err
		}
		if err := insertApprovalActionGroup(ctx, persistence, processDefinitionID, stepID, "StepRejection", step.StepRejectionActions, user); err != nil {
			return err
		}
	}

	type actionGroup struct {
		event   string
		actions *ApprovalActionsXML
	}

	groups := []actionGroup{
		{event: "Submit", actions: xmlData.InitialSubmissionActions},
		{event: "StepApproval", actions: xmlData.StepApprovalActions},
		{event: "FinalApproval", actions: xmlData.FinalApprovalActions},
		{event: "FinalRejection", actions: xmlData.FinalRejectionActions},
		{event: "Recall", actions: xmlData.RecallActions},
		{event: "Reassign", actions: xmlData.ReassignActions},
	}

	for _, group := range groups {
		if err := insertApprovalActionGroup(ctx, persistence, processDefinitionID, "", group.event, group.actions, user); err != nil {
			return err
		}
	}

	return nil
}
