package mysql

import (
	"context"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	domain "hyapp/services/activity-service/internal/domain/wheel"
)

// ClaimWheelRewardOutbox 抢占一个 App 分区内可补偿的转盘发奖事实。
// 行锁和 SKIP LOCKED 允许双节点并行；过期 delivering 可被接管，而仍持有效 lease 的记录不会被第二个节点重复处理。
func (r *Repository) ClaimWheelRewardOutbox(ctx context.Context, workerID string, nowMS int64, lockUntilMS int64, limit int, maxRetry int) ([]domain.RewardOutboxRecord, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	workerID = strings.TrimSpace(workerID)
	if workerID == "" {
		return nil, xerr.New(xerr.InvalidArgument, "wheel reward worker_id is required")
	}
	if limit <= 0 {
		limit = 50
	}
	if maxRetry <= 0 {
		maxRetry = 16
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer func() { _ = tx.Rollback() }()
	appCode := appcode.FromContext(ctx)
	rows, err := tx.QueryContext(ctx, `
		SELECT app_code, outbox_id, payload, status, retry_count, next_retry_at_ms,
		       locked_by, lock_until_ms, last_error, created_at_ms, updated_at_ms
		FROM activity_outbox
		WHERE app_code = ?
		  AND event_type = ?
		  AND retry_count < ?
		  AND (
		        (status IN (?, ?) AND next_retry_at_ms <= ?)
		        OR (status = ? AND lock_until_ms <= ?)
		      )
		ORDER BY created_at_ms, outbox_id
		LIMIT ?
		FOR UPDATE SKIP LOCKED`,
		appCode,
		domain.EventTypeWheelRewardSettlement,
		maxRetry,
		domain.OutboxStatusPending,
		domain.OutboxStatusRetryable,
		nowMS,
		domain.OutboxStatusDelivering,
		nowMS,
		limit,
	)
	if err != nil {
		return nil, err
	}
	records := make([]domain.RewardOutboxRecord, 0, limit)
	for rows.Next() {
		var record domain.RewardOutboxRecord
		if err := rows.Scan(
			&record.AppCode,
			&record.OutboxID,
			&record.Payload,
			&record.Status,
			&record.RetryCount,
			&record.NextRetryAtMS,
			&record.LockedBy,
			&record.LockUntilMS,
			&record.LastError,
			&record.CreatedAtMS,
			&record.UpdatedAtMS,
		); err != nil {
			_ = rows.Close()
			return nil, err
		}
		records = append(records, record)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	for index := range records {
		result, err := tx.ExecContext(ctx, `
			UPDATE activity_outbox
			SET status = ?, locked_by = ?, lock_until_ms = ?, updated_at_ms = ?
			WHERE app_code = ? AND outbox_id = ?`,
			domain.OutboxStatusDelivering,
			workerID,
			lockUntilMS,
			nowMS,
			records[index].AppCode,
			records[index].OutboxID,
		)
		if err != nil {
			return nil, err
		}
		if affected, err := result.RowsAffected(); err != nil || affected != 1 {
			if err != nil {
				return nil, err
			}
			return nil, xerr.New(xerr.Conflict, "wheel reward outbox claim lost")
		}
		records[index].Status = domain.OutboxStatusDelivering
		records[index].LockedBy = workerID
		records[index].LockUntilMS = lockUntilMS
	}
	if err := tx.Commit(); err != nil {
		return nil, err
	}
	return records, nil
}

// MarkWheelRewardOutboxDelivered 把钱包返回的既有或新建收据与 draw 状态、统计和 outbox 在一个 MySQL 事务中提交。
// locked_by 是 fencing token；worker lease 过期并被接管后，旧 worker 即使晚返回也不能覆盖新 owner 的处理结果。
func (r *Repository) MarkWheelRewardOutboxDelivered(ctx context.Context, outboxID string, workerID string, drawIDs []string, transactionID string, nowMS int64) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	outboxID = strings.TrimSpace(outboxID)
	workerID = strings.TrimSpace(workerID)
	if outboxID == "" || workerID == "" {
		return xerr.New(xerr.InvalidArgument, "wheel reward outbox delivery identity is incomplete")
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer func() { _ = tx.Rollback() }()
	appCode := appcode.FromContext(ctx)
	if err := markWheelDrawsGrantedTx(ctx, tx, appCode, drawIDs, transactionID, nowMS, true); err != nil {
		return err
	}
	result, err := tx.ExecContext(ctx, `
		UPDATE activity_outbox
		SET status = ?, locked_by = '', lock_until_ms = 0, last_error = '', updated_at_ms = ?
		WHERE app_code = ? AND outbox_id = ? AND event_type = ? AND status = ? AND locked_by = ?`,
		domain.OutboxStatusDelivered,
		nowMS,
		appCode,
		outboxID,
		domain.EventTypeWheelRewardSettlement,
		domain.OutboxStatusDelivering,
		workerID,
	)
	if err != nil {
		return err
	}
	if affected, err := result.RowsAffected(); err != nil || affected != 1 {
		if err != nil {
			return err
		}
		return xerr.New(xerr.Conflict, "wheel reward outbox delivery lock lost")
	}
	return tx.Commit()
}

// MarkWheelRewardOutboxFailed 释放失败 lease，并以有界重试次数决定 retryable 或 failed。
func (r *Repository) MarkWheelRewardOutboxFailed(ctx context.Context, outboxID string, workerID string, nowMS int64, nextRetryAtMS int64, maxRetry int, lastError string) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	if maxRetry <= 0 {
		maxRetry = 16
	}
	result, err := r.db.ExecContext(ctx, `
		UPDATE activity_outbox
		SET status = CASE WHEN retry_count + 1 >= ? THEN ? ELSE ? END,
		    next_retry_at_ms = CASE WHEN retry_count + 1 >= ? THEN 0 ELSE ? END,
		    retry_count = retry_count + 1,
		    locked_by = '',
		    lock_until_ms = 0,
		    last_error = ?,
		    updated_at_ms = ?
		WHERE app_code = ? AND outbox_id = ? AND event_type = ? AND status = ? AND locked_by = ?`,
		maxRetry,
		domain.OutboxStatusFailed,
		domain.OutboxStatusRetryable,
		maxRetry,
		nextRetryAtMS,
		truncateWheelOutboxError(lastError),
		nowMS,
		appcode.FromContext(ctx),
		strings.TrimSpace(outboxID),
		domain.EventTypeWheelRewardSettlement,
		domain.OutboxStatusDelivering,
		strings.TrimSpace(workerID),
	)
	if err != nil {
		return err
	}
	if affected, err := result.RowsAffected(); err != nil || affected != 1 {
		if err != nil {
			return err
		}
		return xerr.New(xerr.Conflict, "wheel reward outbox failure lock lost")
	}
	return nil
}

func truncateWheelOutboxError(message string) string {
	message = strings.TrimSpace(message)
	if len(message) <= 512 {
		return message
	}
	return message[:512]
}
