package wecom

import (
	"context"
	stdErrors "errors"

	"github.com/bytedance/sonic"
	"github.com/hibiken/asynq"

	approvalapp "github.com/break/junhong_cmp_fiber/internal/application/approval"
	"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// ApprovalSubmissionConsumer 消费通用审批提交 Outbox，并保证结果未知时不盲目重提。
type ApprovalSubmissionConsumer struct {
	repository  *ApprovalContextRepository
	client      *ApprovalSubmissionClient
	attachments approvalAttachmentPort
}

// NewApprovalSubmissionConsumer 创建企业微信审批提交消费者。
func NewApprovalSubmissionConsumer(repository *ApprovalContextRepository, client *ApprovalSubmissionClient, attachments approvalAttachmentPort) *ApprovalSubmissionConsumer {
	return &ApprovalSubmissionConsumer{repository: repository, client: client, attachments: attachments}
}

// Consume 校验通用事件并提交一次企业微信审批申请。
func (c *ApprovalSubmissionConsumer) Consume(ctx context.Context, envelope outbox.DeliveryEnvelope) error {
	if c == nil || c.repository == nil || c.client == nil {
		return errors.New(errors.CodeServiceUnavailable, "企业微信审批提交消费者未配置")
	}
	if envelope.PayloadVersion != constants.ApprovalSubmissionPayloadVersionV1 {
		return stdErrors.Join(asynq.SkipRetry, errors.New(errors.CodeInvalidParam, "不支持的审批提交事件版本"))
	}
	var event approvalapp.SubmissionRequestedEvent
	if err := sonic.Unmarshal(envelope.Payload, &event); err != nil {
		return stdErrors.Join(asynq.SkipRetry, errors.New(errors.CodeInvalidParam, "审批提交事件载荷无效"))
	}
	if event.InstanceID == 0 || event.Provider != constants.IntegrationProviderWeCom {
		return stdErrors.Join(asynq.SkipRetry, errors.New(errors.CodeInvalidParam, "审批提交事件渠道或实例无效"))
	}
	record, claimed, err := c.repository.ClaimSubmission(ctx, event.InstanceID)
	if err != nil {
		return err
	}
	if !claimed {
		return nil
	}
	form, err := buildApprovalForm(
		ctx, record.Context.ApplicationID, event.InstanceID,
		record.Context.ControlMapping, record.Context.TemplateSnapshot, record.Instance.RequestSnapshot, c.attachments,
	)
	if err != nil {
		var attachmentErr *attachmentPreparationError
		if stdErrors.As(err, &attachmentErr) {
			if releaseErr := c.repository.ReleaseForRetry(ctx, event.InstanceID, "审批附件准备失败，等待重试"); releaseErr != nil {
				return releaseErr
			}
			return err
		}
		if markErr := c.repository.MarkFailed(ctx, event.InstanceID, err.Error()); markErr != nil {
			return markErr
		}
		return nil
	}
	result, submitErr := c.client.Submit(ctx, ApprovalSubmitRequest{
		InstanceID: event.InstanceID, ApplicationID: record.Context.ApplicationID,
		TemplateID: record.Context.TemplateID, CreatorUserID: record.Context.CreatorUserID,
		CorrelationID: event.CorrelationID, Contents: form.Contents,
	})
	switch result.Outcome {
	case submissionOutcomeSuccess:
		if err := c.repository.MarkSubmitted(ctx, event.InstanceID, result.SPNo); err != nil {
			return err
		}
	case submissionOutcomeUnknown:
		if err := c.repository.MarkUnknown(ctx, event.InstanceID, result.Message); err != nil {
			return err
		}
	case submissionOutcomeFailed:
		if result.SafeToRetry {
			if err := c.repository.ReleaseForRetry(ctx, event.InstanceID, result.Message); err != nil {
				return err
			}
			if submitErr != nil {
				return submitErr
			}
			return errors.New(errors.CodeServiceUnavailable, result.Message)
		}
		if err := c.repository.MarkFailed(ctx, event.InstanceID, result.Message); err != nil {
			return err
		}
	default:
		return errors.New(errors.CodeInternalError, "企业微信审批提交结果无效")
	}
	return submitErr
}

var _ outbox.EventConsumer = (*ApprovalSubmissionConsumer)(nil)
