package app

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/logx"
	"hyapp/services/wallet-service/internal/config"
	mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql"
)

func requireWalletOutboxPurgeAuthorizations(ctx context.Context, repository *mysqlstorage.Repository, cfg config.OutboxArchiveConfig) error {
	if !cfg.PurgeEnabled {
		return nil
	}
	if repository == nil {
		return errors.New("wallet outbox purge repository is not configured")
	}
	for _, proof := range cfg.PurgeRestoreProofs {
		proofCtx := appcode.WithContext(ctx, proof.AppCode)
		if err := repository.RequireWalletOutboxPurgeAuthorization(proofCtx, proof.AppCode, proof.BatchID, proof.ManifestSHA256); err != nil {
			// App 启动失败比“配置显示 purge=true、实际没有恢复凭证”更安全；滚动发布会停在第一台并保留另一台服务流量。
			return fmt.Errorf("wallet outbox purge authorization rejected for app %s: %w", proof.AppCode, err)
		}
	}
	return nil
}

func (a *App) startWalletOutboxPurgeWorker(ctx context.Context) {
	a.workers.Add(1)
	go func() {
		defer a.workers.Done()
		ticker := time.NewTicker(a.outboxArchiveCfg.PurgePollInterval)
		defer ticker.Stop()
		for {
			if err := a.runWalletOutboxPurgeRound(ctx); err != nil && !errors.Is(err, context.Canceled) {
				logx.Error(ctx, "wallet_outbox_purge_round_failed", err)
			}
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
			}
		}
	}()
}

func (a *App) runWalletOutboxPurgeRound(ctx context.Context) error {
	if a.mysqlRepo == nil {
		return nil
	}
	now := time.Now().UTC()
	cutoffMS := now.Add(-a.outboxArchiveCfg.PurgeMaxAge).UnixMilli()
	receiptVerifiedBeforeMS := now.Add(-a.outboxArchiveCfg.PurgeReceiptGrace).UnixMilli()
	for _, targetAppCode := range a.outboxArchiveCfg.PurgeAppCodes {
		tenantCtx := appcode.WithContext(ctx, targetAppCode)
		lockCtx, lockCancel := context.WithTimeout(tenantCtx, a.outboxArchiveCfg.QueryTimeout)
		release, acquired, err := a.mysqlRepo.AcquireWalletOutboxArchiveAppLock(lockCtx)
		lockCancel()
		if err != nil {
			logx.Error(ctx, "wallet_outbox_purge_app_lock_failed", err, slog.String("app_code", targetAppCode))
			continue
		}
		if !acquired {
			continue
		}

		purgeCtx, purgeCancel := context.WithTimeout(tenantCtx, a.outboxArchiveCfg.QueryTimeout)
		receipt, purgeErr := a.mysqlRepo.PurgeDeliveredWalletOutboxBatch(
			purgeCtx,
			targetAppCode,
			a.outboxArchiveCfg.PurgePolicyVersion,
			cutoffMS,
			receiptVerifiedBeforeMS,
			a.outboxArchiveCfg.PurgeBatchSize,
			now.UnixMilli(),
		)
		purgeCancel()
		release()
		if purgeErr != nil {
			// 单 App 失败不能扩大到其他租户；事务会回滚，下一轮仍从同一 exact member 重试。
			logx.Error(ctx, "wallet_outbox_purge_app_failed", purgeErr, slog.String("app_code", targetAppCode))
			continue
		}
		if receipt.DeletedCount == 0 {
			continue
		}
		logx.Info(ctx, "wallet_outbox_purged",
			slog.String("app_code", receipt.AppCode),
			slog.String("purge_id", receipt.PurgeID),
			slog.String("policy_version", receipt.PolicyVersion),
			slog.Int("deleted_count", receipt.DeletedCount),
			slog.Int64("cutoff_ms", receipt.CutoffMS),
			slog.Int64("receipt_verified_before_ms", receipt.ReceiptVerifiedBeforeMS),
			slog.Int64("first_updated_at_ms", receipt.FirstCursor.UpdatedAtMS),
			slog.String("first_event_id", receipt.FirstCursor.EventID),
			slog.Int64("last_updated_at_ms", receipt.LastCursor.UpdatedAtMS),
			slog.String("last_event_id", receipt.LastCursor.EventID),
			slog.String("candidate_sha256", receipt.CandidateSHA256),
		)
	}
	return nil
}
