// Package confirmnotice consumes activity-service confirm-message facts and delivers C2C IM.
package confirmnotice

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"strconv"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/logx"
	"hyapp/pkg/messagemq"
	"hyapp/pkg/tencentim"
)

const (
	sourceMessageActionOutbox = "message_action_outbox"
	channelTencentIMC2C       = "tencent_im_c2c"
	noticeTypeIMConfirm       = "im_confirm"
)

// Repository owns notice_delivery_events rows for confirm IM delivery.
type Repository interface {
	ClaimConfirmDelivery(ctx context.Context, workerID string, delivery Delivery, lockTTL time.Duration) (Delivery, bool, error)
	MarkConfirmDelivered(ctx context.Context, delivery Delivery, deliveredPayload []byte, nowMs int64) error
	MarkConfirmRetryable(ctx context.Context, delivery Delivery, retryCount int, nextRetryAtMS int64, lastErr string, nowMs int64) error
	MarkConfirmFailed(ctx context.Context, delivery Delivery, retryCount int, lastErr string, nowMs int64) error
}

// Publisher is the Tencent IM C2C boundary.
type Publisher interface {
	PublishUserCustomMessage(ctx context.Context, message tencentim.CustomUserMessage) error
}

// Config carries process-level metadata for worker IDs.
type Config struct {
	NodeID string
}

// Delivery is one concrete C2C IM side effect derived from message_action_outbox.
type Delivery struct {
	AppCode      string
	EventID      string
	EventType    string
	ConfirmID    string
	Channel      string
	NoticeType   string
	SenderUserID int64
	TargetUserID int64
	PayloadJSON  []byte
	RetryCount   int
	CreatedAtMS  int64
}

// Service maps confirm-message outbox facts to Tencent IM custom C2C messages.
type Service struct {
	cfg        Config
	repository Repository
	publisher  Publisher
}

func New(cfg Config, repository Repository, publisher Publisher) *Service {
	return &Service{cfg: cfg, repository: repository, publisher: publisher}
}

// ProcessActionOutboxMessage handles one confirm-message MQ fact and acks unrelated messages.
func (s *Service) ProcessActionOutboxMessage(ctx context.Context, message messagemq.ActionOutboxMessage, options WorkerOptions) (bool, error) {
	if message.EventType != "ConfirmMessageCreated" {
		return false, nil
	}
	options = normalizeWorkerOptions(options, s.cfg.NodeID)
	if s.repository == nil {
		return true, fmt.Errorf("notice repository is not configured")
	}
	if s.publisher == nil {
		return true, fmt.Errorf("notice publisher is not configured")
	}
	delivery, err := deliveryFromMessage(message)
	if err != nil {
		return true, err
	}
	claimed, ok, err := s.repository.ClaimConfirmDelivery(ctx, options.WorkerID, delivery, options.LockTTL)
	if err != nil || !ok {
		return true, err
	}
	return true, s.publishDelivery(ctx, claimed, options)
}

func (s *Service) publishDelivery(ctx context.Context, delivery Delivery, options WorkerOptions) error {
	publishCtx, cancel := context.WithTimeout(ctx, options.PublishTimeout)
	defer cancel()
	err := s.publisher.PublishUserCustomMessage(publishCtx, tencentim.CustomUserMessage{
		ToAccount:   tencentim.FormatUserID(delivery.TargetUserID),
		FromAccount: tencentim.FormatUserID(delivery.SenderUserID),
		EventID:     delivery.EventID,
		Desc:        delivery.NoticeType,
		Ext:         "im_confirm",
		PayloadJSON: delivery.PayloadJSON,
	})
	nowMs := time.Now().UnixMilli()
	if err == nil {
		if markErr := s.repository.MarkConfirmDelivered(ctx, delivery, delivery.PayloadJSON, nowMs); markErr != nil {
			return markErr
		}
		logx.Info(ctx, "notice_confirm_delivered",
			slog.String("event_id", delivery.EventID),
			slog.String("app_code", delivery.AppCode),
			slog.String("confirm_id", delivery.ConfirmID),
			slog.Int64("target_user_id", delivery.TargetUserID),
		)
		return nil
	}
	nextRetryCount := delivery.RetryCount + 1
	if nextRetryCount >= options.MaxRetryCount {
		if markErr := s.repository.MarkConfirmFailed(ctx, delivery, nextRetryCount, err.Error(), nowMs); markErr != nil {
			return markErr
		}
		logx.Error(ctx, "notice_confirm_dead_letter", err, slog.String("event_id", delivery.EventID), slog.String("confirm_id", delivery.ConfirmID))
		return nil
	}
	nextRetryAtMS := nowMs + backoff(nextRetryCount, options).Milliseconds()
	if markErr := s.repository.MarkConfirmRetryable(ctx, delivery, nextRetryCount, nextRetryAtMS, err.Error(), nowMs); markErr != nil {
		return markErr
	}
	return err
}

type confirmPayload struct {
	SenderUserID   string `json:"sender_user_id"`
	ReceiverUserID string `json:"receiver_user_id"`
}

func deliveryFromMessage(message messagemq.ActionOutboxMessage) (Delivery, error) {
	var payload confirmPayload
	if err := json.Unmarshal([]byte(message.PayloadJSON), &payload); err != nil {
		return Delivery{}, err
	}
	senderID, err := strconv.ParseInt(payload.SenderUserID, 10, 64)
	if err != nil || senderID <= 0 {
		return Delivery{}, fmt.Errorf("sender_user_id is invalid")
	}
	receiverID, err := strconv.ParseInt(payload.ReceiverUserID, 10, 64)
	if err != nil || receiverID <= 0 {
		return Delivery{}, fmt.Errorf("receiver_user_id is invalid")
	}
	return Delivery{
		AppCode:      appcode.Normalize(message.AppCode),
		EventID:      message.EventID,
		EventType:    message.EventType,
		ConfirmID:    message.ConfirmID,
		Channel:      channelTencentIMC2C,
		NoticeType:   noticeTypeIMConfirm,
		SenderUserID: senderID,
		TargetUserID: receiverID,
		PayloadJSON:  []byte(message.PayloadJSON),
		CreatedAtMS:  message.OccurredAtMS,
	}, nil
}
