package workers

import (
	"strconv"
	"strings"
	"time"

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

const S3BillsDetailWorkerName = "S3BillsDetailExport"

func S3BillsDetailExportWorker() {
	if !config.Get().GetBool(config.EnableS3BillsDetailExport) {
		return
	}
	tenantList := config.Get().GetStringSlice(config.S3BillsDetailTenantList)
	if len(tenantList) == 0 {
		return
	}
	now := timeutils.GetNow()
	if now.Hour() < 4 {
		log.Info("worker S3BillsDetailExport skipped, before 04:00 UTC", zap.Time("now", now))
		return
	}
	workName := S3BillsDetailWorkerName

	for _, combinedID := range tenantList {
		parts := strings.Split(combinedID, "-")
		if len(parts) != 2 {
			log.Error("invalid tenant config format", zap.String("val", combinedID))
			continue
		}
		tID64, err1 := strconv.ParseUint(parts[0], 10, 64)
		bID64, err2 := strconv.ParseUint(parts[1], 10, 64)
		if err1 != nil || err2 != nil {
			log.Error("failed to parse ID", zap.String("val", combinedID), zap.Error(err1), zap.Error(err2))
			continue
		}
		tenantID := uint(tID64)
		billingConfigID := uint(bID64)

		workerhandler.Get().DedupeRun(tenantID, workName, timeutils.DAY, now, func(t uint, startTime time.Time) (interface{}, error) {
			return nil, runExport(tenantID, billingConfigID, startTime)
		})
	}
}

func runExport(tenantID uint, billingConfigID uint, now time.Time) error {
	maxDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).AddDate(0, 0, -1)

	months := []time.Time{
		timeutils.GetMonthDate(maxDate),
		timeutils.GetMonthDate(maxDate).AddDate(0, -1, 0),
	}

	model := &costexplorer.CostExplorerModel{}

	billingConfigs, err := getActiveBillingConfigs(tenantID, billingConfigID)
	if err != nil {
		log.Error("failed to get billing configs", zap.Error(err))
		return err
	}

	for _, m := range months {
		for _, bcID := range billingConfigs {
			if err := exportMonthByDay(model, tenantID, bcID, m, maxDate); err != nil {
				log.Error("failed to export month by day",
					zap.Uint("tenantID", tenantID),
					zap.Uint("billingConfigID", bcID),
					zap.Error(err))
			}
		}
	}
	return nil
}

func exportMonthByDay(model *costexplorer.CostExplorerModel, tenantID uint, billingConfigID uint, month time.Time, maxDate time.Time) error {
	year := month.Year()
	monthInt := int(month.Month())
	daysInMonth := time.Date(year, time.Month(monthInt)+1, 0, 0, 0, 0, 0, time.UTC).Day()

	for day := 1; day <= daysInMonth; day++ {
		date := time.Date(year, time.Month(monthInt), day, 0, 0, 0, 0, time.UTC)
		if date.After(maxDate) {
			break
		}

		dateStr := date.Format("2006-01-02")
		key := awshandler.BuildS3BillsDetailKey(tenantID, dateStr)

		exists, err := awshandler.CheckS3BillsDetailExists(key)
		if err != nil {
			log.Error("failed to check s3 bills detail exists",
				zap.Uint("tenantID", tenantID),
				zap.String("date", dateStr),
				zap.Error(err))
			continue
		}
		if exists {
			continue
		}

		jsonlData, err := model.GetBillsDetailsForDateStarterBatched(tenantID, billingConfigID, date)
		if err != nil {
			log.Error("failed to get bills details for date",
				zap.Uint("tenantID", tenantID),
				zap.Uint("billingConfigID", billingConfigID),
			zap.String("date", dateStr),
				zap.Error(err))
			continue
		}

		if len(jsonlData) == 0 {
			continue
		}

		if err := awshandler.WriteBillsDetailJSONLToS3(key, jsonlData); err != nil {
			log.Error("failed to put bills detail to S3",
				zap.Uint("tenantID", tenantID),
				zap.String("date", dateStr),
				zap.Error(err))
		}
	}
	return nil
}

func getActiveBillingConfigs(tenantID uint, hintBillingConfigID uint) ([]uint, error) {
	if hintBillingConfigID != 0 {
		return []uint{hintBillingConfigID}, nil
	}
	bcModel := &billingconfig.BillingConfigModel{}
	activeID, err := bcModel.GetActiveBillingConfigID(tenantID)
	if err != nil {
		return nil, err
	}
	if activeID != 0 {
		return []uint{activeID}, nil
	}
	return nil, nil
}
