package payment

import (
	"context"
	"log/slog"
	"strings"
	"sync"
	"time"

	"hyapp-admin-server/internal/config"
)

type legacyGooglePaidSyncSource interface {
	AppCode() string
	SupportsGooglePaidSync() bool
	ListPendingGooglePaidTransactionIDs(ctx context.Context, limit int) ([]string, error)
	RefreshGooglePaidDetails(ctx context.Context, transactionIDs []string) ([]googleRechargePaidDTO, error)
}

type legacyGooglePaidSyncAttemptStore interface {
	RecordLegacyGooglePaidSyncFailure(appCode string, transactionID string, message string, nowMS int64) error
	ClearLegacyGooglePaidSyncFailure(appCode string, transactionID string) error
}

// LegacyGooglePaidSyncWorker polls every 15 seconds independently of finance-page traffic.
// Successful rows leave the pending set through the paid cache; failures remain durable with backoff.
type LegacyGooglePaidSyncWorker struct {
	batchSize      int
	pollInterval   time.Duration
	requestTimeout time.Duration
	sources        []legacyGooglePaidSyncSource
	store          legacyGooglePaidSyncAttemptStore

	closeOnce sync.Once
	done      chan struct{}
	stop      chan struct{}
}

func NewLegacyGooglePaidSyncWorker(sources []RechargeBillSource, store legacyGooglePaidSyncAttemptStore, cfg config.FinanceGooglePaidSyncConfig) *LegacyGooglePaidSyncWorker {
	narrow := make([]legacyGooglePaidSyncSource, 0, len(sources))
	for _, source := range sources {
		if candidate, ok := source.(legacyGooglePaidSyncSource); ok && candidate.SupportsGooglePaidSync() {
			narrow = append(narrow, candidate)
		}
	}
	return newLegacyGooglePaidSyncWorker(narrow, store, cfg)
}

func newLegacyGooglePaidSyncWorker(sources []legacyGooglePaidSyncSource, store legacyGooglePaidSyncAttemptStore, cfg config.FinanceGooglePaidSyncConfig) *LegacyGooglePaidSyncWorker {
	return &LegacyGooglePaidSyncWorker{
		batchSize:      cfg.BatchSize,
		pollInterval:   cfg.PollInterval,
		requestTimeout: cfg.RequestTimeout,
		sources:        sources,
		store:          store,
		done:           make(chan struct{}),
		stop:           make(chan struct{}),
	}
}

func (w *LegacyGooglePaidSyncWorker) Start() {
	if w == nil || w.store == nil || len(w.sources) == 0 {
		return
	}
	go w.loop()
}

func (w *LegacyGooglePaidSyncWorker) Close() {
	if w == nil || w.store == nil || len(w.sources) == 0 {
		return
	}
	w.closeOnce.Do(func() {
		close(w.stop)
		<-w.done
	})
}

func (w *LegacyGooglePaidSyncWorker) loop() {
	defer close(w.done)
	ticker := time.NewTicker(w.pollInterval)
	defer ticker.Stop()
	w.runOnce(context.Background())
	for {
		select {
		case <-ticker.C:
			w.runOnce(context.Background())
		case <-w.stop:
			return
		}
	}
}

func (w *LegacyGooglePaidSyncWorker) runOnce(parent context.Context) {
	for _, source := range w.sources {
		// Ten full batches per tick drain historical data quickly without turning the steady-state
		// 15-second poller into an unbounded loop when a provider keeps returning pathological data.
		for drainBatch := 0; drainBatch < 10; drainBatch++ {
			ctx, cancel := context.WithTimeout(parent, w.requestTimeout)
			transactionIDs, err := source.ListPendingGooglePaidTransactionIDs(ctx, w.batchSize)
			if err != nil {
				cancel()
				slog.Error("legacy_google_paid_sync_scan_failed", "app_code", source.AppCode(), "error", err.Error())
				break
			}
			if len(transactionIDs) == 0 {
				cancel()
				break
			}
			results, err := source.RefreshGooglePaidDetails(ctx, transactionIDs)
			cancel()
			if err != nil {
				slog.Error("legacy_google_paid_sync_batch_failed", "app_code", source.AppCode(), "count", len(transactionIDs), "error", err.Error())
				break
			}
			nowMS := time.Now().UnixMilli()
			succeeded := 0
			failed := 0
			for _, result := range results {
				transactionID := strings.TrimSpace(result.TransactionID)
				if transactionID == "" {
					continue
				}
				if strings.TrimSpace(result.Error) == "" {
					if err := w.store.ClearLegacyGooglePaidSyncFailure(source.AppCode(), transactionID); err != nil {
						slog.Error("legacy_google_paid_sync_retry_clear_failed", "app_code", source.AppCode(), "transaction_id", transactionID, "error", err.Error())
					}
					succeeded++
					continue
				}
				failed++
				if err := w.store.RecordLegacyGooglePaidSyncFailure(source.AppCode(), transactionID, result.Error, nowMS); err != nil {
					slog.Error("legacy_google_paid_sync_retry_store_failed", "app_code", source.AppCode(), "transaction_id", transactionID, "error", err.Error())
				}
			}
			slog.Info("legacy_google_paid_sync_batch_completed", "app_code", source.AppCode(), "claimed", len(transactionIDs), "succeeded", succeeded, "failed", failed)
			if len(transactionIDs) < w.batchSize {
				break
			}
		}
	}
}
