package services

import (
	"context"
	"fmt"

	"github.com/nexuscrm/automation"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

type approvalEngine interface {
	Submit(ctx context.Context, req automation.SubmitRequest, user *models.UserSession) (models.SObject, error)
	Approve(ctx context.Context, workItemID, comments string, user *models.UserSession) (models.SObject, error)
	Reject(ctx context.Context, workItemID, comments string, user *models.UserSession) (models.SObject, error)
	Recall(ctx context.Context, workItemID, comments string, user *models.UserSession) (models.SObject, error)
	Reassign(ctx context.Context, workItemID, newActorID string, user *models.UserSession) error
	ListPendingApprovals(ctx context.Context, user *models.UserSession) ([]automation.PendingApprovalView, error)
	GetLatestProcessInstance(ctx context.Context, recordID string, user *models.UserSession) (*automation.ProcessInstanceSummary, error)
	GetRecordApprovalStepDisplayMetadata(ctx context.Context, recordID string, user *models.UserSession) ([]automation.ApprovalStepDisplayMetadata, error)
	Clone(ctx context.Context, processDefID, newDeveloperName, newLabel string, user *models.UserSession) (*models.SObject, error)
}

type approvalsContract struct {
	engine    approvalEngine
	metadata  flowCatalogMetadata
	protected protectedFlowNamesSource
}

var _ contract.Approvals = (*approvalsContract)(nil)

func (sm *ServiceManager) ApprovalContract() contract.Approvals {
	if sm == nil {
		return nil
	}
	if sm.NeutralApprovals != nil {
		return sm.NeutralApprovals
	}
	if sm.Approval == nil {
		return nil
	}
	return NewApprovalsContract(sm.Approval, sm.Metadata, automation.NewProtectedFlowCatalog(sm.deps.DB))
}

func NewApprovalsContract(engine approvalEngine, metadata flowCatalogMetadata, protected protectedFlowNamesSource) contract.Approvals {
	return &approvalsContract{engine: engine, metadata: metadata, protected: protected}
}

func (c *approvalsContract) Submit(ctx context.Context, req contract.ApprovalSubmitRequest, user *models.UserSession) (contract.Record, error) {
	if c == nil || c.engine == nil {
		return nil, fmt.Errorf("approval service is not configured")
	}
	record, err := c.engine.Submit(ctx, automation.SubmitRequest{
		ObjectAPIName:     req.ObjectAPIName,
		RecordID:          req.RecordID,
		Comments:          req.Comments,
		SkipEntryCriteria: req.SkipEntryCriteria,
	}, user)
	return approvalRecordFromModel(record), err
}

func (c *approvalsContract) Approve(ctx context.Context, workItemID, comments string, user *models.UserSession) (contract.Record, error) {
	if c == nil || c.engine == nil {
		return nil, fmt.Errorf("approval service is not configured")
	}
	record, err := c.engine.Approve(ctx, workItemID, comments, user)
	return approvalRecordFromModel(record), err
}

func (c *approvalsContract) Reject(ctx context.Context, workItemID, comments string, user *models.UserSession) (contract.Record, error) {
	if c == nil || c.engine == nil {
		return nil, fmt.Errorf("approval service is not configured")
	}
	record, err := c.engine.Reject(ctx, workItemID, comments, user)
	return approvalRecordFromModel(record), err
}

func (c *approvalsContract) Recall(ctx context.Context, workItemID, comments string, user *models.UserSession) (contract.Record, error) {
	if c == nil || c.engine == nil {
		return nil, fmt.Errorf("approval service is not configured")
	}
	record, err := c.engine.Recall(ctx, workItemID, comments, user)
	return approvalRecordFromModel(record), err
}

func (c *approvalsContract) Reassign(ctx context.Context, workItemID, newActorID string, user *models.UserSession) error {
	if c == nil || c.engine == nil {
		return fmt.Errorf("approval service is not configured")
	}
	return c.engine.Reassign(ctx, workItemID, newActorID, user)
}

func (c *approvalsContract) ListPendingApprovals(ctx context.Context, user *models.UserSession) ([]contract.ApprovalPendingView, error) {
	if c == nil || c.engine == nil {
		return nil, fmt.Errorf("approval service is not configured")
	}
	items, err := c.engine.ListPendingApprovals(ctx, user)
	if err != nil {
		return nil, err
	}
	out := make([]contract.ApprovalPendingView, 0, len(items))
	for _, item := range items {
		out = append(out, contract.ApprovalPendingView{
			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, nil
}

func (c *approvalsContract) GetLatestProcessInstance(ctx context.Context, recordID string, user *models.UserSession) (*contract.ApprovalProcessInstanceSummary, error) {
	if c == nil || c.engine == nil {
		return nil, fmt.Errorf("approval service is not configured")
	}
	summary, err := c.engine.GetLatestProcessInstance(ctx, recordID, user)
	if err != nil || summary == nil {
		return nil, err
	}
	out := &contract.ApprovalProcessInstanceSummary{
		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([]contract.ApprovalProcessInstancePendingWorkItem, 0, len(summary.PendingWorkItems)),
	}
	for _, item := range summary.PendingWorkItems {
		out.PendingWorkItems = append(out.PendingWorkItems, contract.ApprovalProcessInstancePendingWorkItem{
			ID:                item.ID,
			ActorID:           item.ActorID,
			ActorName:         item.ActorName,
			OriginalActorID:   item.OriginalActorID,
			OriginalActorName: item.OriginalActorName,
		})
	}
	return out, nil
}

func (c *approvalsContract) GetRecordApprovalStepDisplayMetadata(ctx context.Context, recordID string, user *models.UserSession) ([]contract.ApprovalStepDisplayMetadata, error) {
	if c == nil || c.engine == nil {
		return nil, fmt.Errorf("approval service is not configured")
	}
	facts, err := c.engine.GetRecordApprovalStepDisplayMetadata(ctx, recordID, user)
	if err != nil {
		return nil, err
	}
	out := make([]contract.ApprovalStepDisplayMetadata, 0, len(facts))
	for _, fact := range facts {
		out = append(out, contract.ApprovalStepDisplayMetadata{
			ProcessNodeID:    fact.ProcessNodeID,
			StepOrder:        fact.StepOrder,
			StepName:         fact.StepName,
			StepLabel:        fact.StepLabel,
			HasEntryCriteria: fact.HasEntryCriteria,
			IfCriteriaNotMet: fact.IfCriteriaNotMet,
		})
	}
	return out, nil
}

func (c *approvalsContract) Clone(ctx context.Context, processDefID, newProcessName, newLabel string, user *models.UserSession) (contract.Record, error) {
	if c == nil || c.engine == nil {
		return nil, fmt.Errorf("approval service is not configured")
	}
	record, err := c.engine.Clone(ctx, processDefID, newProcessName, newLabel, user)
	if record == nil {
		return nil, err
	}
	return approvalRecordFromModel(*record), err
}

func (c *approvalsContract) ListApprovalNotificationFlows(ctx context.Context) ([]contract.FlowDescriptor, error) {
	if c == nil || c.metadata == nil {
		return nil, fmt.Errorf("approval notification flow catalog is not configured")
	}
	definitions, err := automation.ListApprovalNotificationFlowDefinitions(
		ctx,
		approvalNotificationFlowLegacySource{metadata: c.metadata, protected: c.protected},
	)
	if err != nil {
		return nil, err
	}
	out := make([]contract.FlowDescriptor, 0, len(definitions))
	for _, def := range definitions {
		out = append(out, flowDescriptorFromModel(def))
	}
	return out, nil
}

type approvalNotificationFlowLegacySource struct {
	metadata  flowCatalogMetadata
	protected protectedFlowNamesSource
}

func (source approvalNotificationFlowLegacySource) ApprovalNotificationFlowDefinitions(
	ctx context.Context,
) ([]*models.FlowDefinition, error) {
	return source.metadata.GetFlowDefinitions(ctx), nil
}

func (source approvalNotificationFlowLegacySource) ApprovalNotificationFlowVersion(
	ctx context.Context,
	id string,
) (*models.FlowVersion, error) {
	return source.metadata.GetFlowVersionById(ctx, id), nil
}

func (source approvalNotificationFlowLegacySource) ProtectedFlowNames(
	ctx context.Context,
) ([]string, error) {
	if source.protected == nil {
		return nil, fmt.Errorf("protected flow catalog is not configured")
	}
	return source.protected.ProtectedFlowNames(ctx)
}

func approvalRecordFromModel(in models.SObject) contract.Record {
	if in == nil {
		return nil
	}
	out := make(contract.Record, len(in))
	for key, value := range in {
		if models.IsRecordMetadataKey(key) {
			continue
		}
		if key == "ActionFailures" {
			out[key] = approvalActionFailuresFromModel(value)
			continue
		}
		out[key] = value
	}
	if objectType := models.RecordObjectType(in); objectType != "" {
		out[models.ObjectTypeMetadataKey] = objectType
	}
	return out
}

func approvalActionFailuresFromModel(raw any) []contract.ApprovalProcessActionFailure {
	failures, ok := raw.([]automation.ProcessActionFailure)
	if !ok {
		return nil
	}
	out := make([]contract.ApprovalProcessActionFailure, 0, len(failures))
	for _, failure := range failures {
		out = append(out, contract.ApprovalProcessActionFailure{
			ActionName:   failure.ActionName,
			ActionType:   failure.ActionType,
			TargetObject: failure.TargetObject,
			TargetID:     failure.TargetID,
			Field:        failure.Field,
			Reason:       failure.Reason,
		})
	}
	return out
}
