package mysql

import (
	"context"
	"database/sql"
	"time"

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

func (r *Repository) ClaimActivityTemplateStatsOutbox(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.StatsOutboxRecord, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	if batchSize <= 0 {
		batchSize = 100
	}
	if lockTTL <= 0 {
		lockTTL = 30 * time.Second
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()
	rows, err := tx.QueryContext(ctx, `
		SELECT outbox_id, event_type, payload, retry_count
		FROM activity_template_stats_outbox
		WHERE ((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`, outboxStatusPending, outboxStatusFailed, nowMS, outboxStatusRunning, nowMS, batchSize)
	if err != nil {
		return nil, err
	}
	items := make([]domain.StatsOutboxRecord, 0, batchSize)
	for rows.Next() {
		var item domain.StatsOutboxRecord
		if err := rows.Scan(&item.OutboxID, &item.EventType, &item.Payload, &item.RetryCount); err != nil {
			rows.Close()
			return nil, err
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		rows.Close()
		return nil, err
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	lockUntil := nowMS + lockTTL.Milliseconds()
	for _, item := range items {
		if _, err := tx.ExecContext(ctx, `
			UPDATE activity_template_stats_outbox
			SET status = ?, locked_by = ?, lock_until_ms = ?, updated_at_ms = ?
			WHERE outbox_id = ?`, outboxStatusRunning, workerID, lockUntil, nowMS, item.OutboxID); err != nil {
			return nil, err
		}
	}
	if err := tx.Commit(); err != nil {
		return nil, err
	}
	return items, nil
}

func (r *Repository) MarkActivityTemplateStatsOutboxSent(ctx context.Context, outboxID string, nowMS int64) error {
	_, err := r.db.ExecContext(ctx, `
		UPDATE activity_template_stats_outbox
		SET status = ?, locked_by = '', lock_until_ms = 0, last_error = '', updated_at_ms = ?
		WHERE outbox_id = ?`, outboxStatusSent, nowMS, outboxID)
	return err
}

func (r *Repository) MarkActivityTemplateStatsOutboxFailed(ctx context.Context, outboxID, reason string, nowMS int64) error {
	var retryCount int32
	err := r.db.QueryRowContext(ctx, `SELECT retry_count FROM activity_template_stats_outbox WHERE outbox_id = ?`, outboxID).Scan(&retryCount)
	if err == sql.ErrNoRows {
		return nil
	}
	if err != nil {
		return err
	}
	retryCount++
	backoff := time.Second * time.Duration(1<<minInt32(retryCount, 8))
	_, err = r.db.ExecContext(ctx, `
		UPDATE activity_template_stats_outbox
		SET status = ?, retry_count = ?, next_retry_at_ms = ?, locked_by = '', lock_until_ms = 0,
		    last_error = ?, updated_at_ms = ?
		WHERE outbox_id = ?`, outboxStatusFailed, retryCount, nowMS+backoff.Milliseconds(),
		truncateActivityTemplateRuntimeError(reason), nowMS, outboxID)
	return err
}

func minInt32(left, right int32) int32 {
	if left < right {
		return left
	}
	return right
}
