package workers

import (
	"time"

	"github.com/pingcap/dbaas/billing2/handler/awshandler"
	"github.com/pingcap/dbaas/billing2/handler/workerhandler"
	"github.com/pingcap/dbaas/billing2/model/payment/reconciliation"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"go.uber.org/zap"
)

// shouldRunEstimatedWorker checks whether the estimated AR table worker should run now,
// and if so, returns the target billing month date (always the previous month).
func shouldRunEstimatedWorker(now time.Time) (bool, time.Time) {
	// Run on 1st or 2nd of month (2nd as fallback for retries after restart/error).
	if now.Day() != 1 && now.Day() != 2 {
		return false, time.Time{}
	}
	return true, timeutils.GetMonthDate(now.AddDate(0, -1, 0))
}

func AREstimatedWorker() error {
	now := timeutils.GetNow()
	ok, date := shouldRunEstimatedWorker(now)
	if !ok {
		return nil
	}
	workerName := "AREstimatedWorker"
	// dedupe key uses `now` (the execution day) on purpose: it must stay
	// stable across code revisions that may rename the underlying
	// billing-period variable. Keys derived from the billing month (e.g.
	// `date`) collide across deploy boundaries — see commit b5e3b46d /
	// #1232 vs #1238 history, where 2026-06-01 (old key "...-2026-06")
	// masked the 2026-07-01 natural tick. Use `now` and keep this aligned
	// with ARActualWorker.
	workerhandler.Get().DedupeRun(0, workerName, timeutils.MONTH, now, func(tenantID uint, startTime time.Time) (interface{}, error) {
		workerStart := time.Now()
		log.Info("start generating estimated AR table", zap.Time("date", date))
		model := &reconciliation.ARTableModel{}
		rows, err := model.GenerateEstimatedARTable(date)
		if err != nil {
			log.Error("failed to generate estimated AR table", zap.Error(err))
			return nil, err
		}
		err = model.SaveARTableToDB(rows, date)
		if err != nil {
			log.Error("failed to save estimated AR table to DB", zap.Error(err))
			return nil, err
		}
		ret, err := model.RenderARTable(rows)
		if err != nil {
			return nil, err
		}
		key := awshandler.BuildS3ARTableKey(date, "estimated")
		err = model.PutARTableResultToS3(key, ret)
		if time.Since(workerStart) > time.Hour {
			log.Error("estimated AR table generation exceeded 1 hour",
				zap.Duration("elapsed", time.Since(workerStart)),
				zap.Time("date", date))
		}
		return nil, err
	})
	return nil
}

func ARActualWorker() error {
	now := timeutils.GetNow()
	if now.Day() != 4 {
		return nil
	}
	date := now.AddDate(0, -1, 0)
	workerName := "ARActualWorker"
	workerhandler.Get().DedupeRun(0, workerName, timeutils.MONTH, now, func(tenantID uint, startTime time.Time) (interface{}, error) {
		log.Info("start generating actual AR table", zap.Time("date", date))
		model := &reconciliation.ARTableModel{}
		rows, err := model.GenerateARInitTable(date)
		if err != nil {
			log.Error("failed to generate actual AR table", zap.Error(err))
			return nil, err
		}
		err = model.SaveARTableToDB(rows, date)
		if err != nil {
			log.Error("failed to save actual AR table to DB", zap.Error(err))
			return nil, err
		}
		ret, err := model.RenderARTable(rows)
		if err != nil {
			return nil, err
		}
		key := awshandler.BuildS3ARTableKey(date, "actual")
		return nil, model.PutARTableResultToS3(key, ret)
	})
	return nil
}
