package statistic

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/pingcap/log"
	"github.com/tidbcloud/observability/o11ymetering/pkg/common/constants"
	"github.com/tidbcloud/observability/o11ymetering/pkg/common/message"
	"github.com/tidbcloud/observability/o11ymetering/pkg/common/models"
	"github.com/tidbcloud/observability/o11ymetering/pkg/facade/database"
	"github.com/tidbcloud/observability/o11ymetering/pkg/facade/iface"
	"github.com/tidbcloud/observability/o11ymetering/pkg/service/statistic/handler"
	"go.uber.org/zap"
)

func ReplayStatisticTasks(ctx context.Context, req message.ReplayStatisticTasksReq) (message.ReplayStatisticTasksResp, error) {
	var resp message.ReplayStatisticTasksResp

	// Calculate expected task count and periods
	periodCount := (req.EndTimestamp - req.StartTimestamp) / constants.StatisticStepSeconds
	taskBuilders := getFilteredTaskHandlers(req)
	expectedTaskCount := int(periodCount) * len(taskBuilders)

	// Start async replay in background
	go executeReplayAsync(context.WithoutCancel(ctx), req)

	// Return immediately with expected counts
	resp.TasksGenerated = expectedTaskCount
	resp.Message = fmt.Sprintf("Replay started asynchronously for %d periods with %d task types", periodCount, len(taskBuilders))

	return resp, nil
}

func executeReplayAsync(ctx context.Context, req message.ReplayStatisticTasksReq) {
	log.Info("Starting async replay execution: " + fmt.Sprintf("start=%d, end=%d", req.StartTimestamp, req.EndTimestamp))

	// Determine the target month timestamp for merge table
	targetMonthTimestamp := getMonthStartTimestamp(req.StartTimestamp)

	// Create a dedicated merge reader/writer for replay (avoid singleton interference)
	replayMergeRW := createReplayMergeReaderWriter(ctx, targetMonthTimestamp)
	defer closeReplayReaderWriter(replayMergeRW)
	logger := log.With(zap.Any("request", req))

	// Get task handlers based on filters
	taskBuilders := getFilteredTaskHandlers(req)
	logger.Info("pick taskHandlers", zap.Int("count", len(taskBuilders)))

	successCount := 0
	failureCount := 0

	// Generate periods for the time range
	for timestamp := req.StartTimestamp; timestamp < req.EndTimestamp; timestamp += constants.StatisticStepSeconds {
		// Calculate and log replay progress
		progressPercent := float64(timestamp-req.StartTimestamp) / float64(req.EndTimestamp-req.StartTimestamp) * 100

		logger.Info("replay progress",
			zap.Int64("timestamp", timestamp),
			zap.Float64("progressPercent", progressPercent))
		period := string(models.FromStartTime(time.Unix(timestamp, 0)))

		// Generate and execute tasks for this period
		for _, taskBuilder := range taskBuilders {
			task, err := taskBuilder.Build(ctx, period)
			if err != nil {
				log.Error("Failed to build task for replay: " + period + ", error: " + err.Error())
				failureCount++
				continue
			}

			// Execute task handler directly to collect data
			taskHandler := pickHandler(task.StatisticClass, task.MeteringGroup, task.Env)
			records, executeErr := taskHandler.Execute(ctx, task)
			if executeErr != nil {
				log.Error("Failed to execute replay task: " + task.ID + ", period: " + period + ", error: " + executeErr.Error())
				failureCount++
				continue
			}

			// Write records directly to merge_statistic database
			err = saveRecordsToMergeDB(ctx, replayMergeRW, records)
			if err != nil {
				log.Error("Failed to save records to merge table: " + task.ID + ", period: " + period + ", error: " + err.Error())
				failureCount++
				continue
			}

			successCount++
		}
	}

	log.Info(fmt.Sprintf("Replay completed: success=%d, failure=%d", successCount, failureCount))
}

func getFilteredTaskHandlers(req message.ReplayStatisticTasksReq) []handler.TaskHandler {
	handlers := []handler.TaskHandler{}

	// If no filters specified, use all configured tasks
	if req.StatisticClass == nil && req.MeteringGroup == nil && req.Env == nil {
		return listTasksHandler()
	}

	// Build filtered list based on request filters
	for _, t := range getTasks() {
		// Apply filters
		if req.StatisticClass != nil && t.StatisticClass != *req.StatisticClass {
			continue
		}
		if req.MeteringGroup != nil && t.MeteringGroup != *req.MeteringGroup {
			continue
		}
		if req.Env != nil && t.Env != *req.Env {
			continue
		}

		handlers = append(handlers, pickHandler(t.StatisticClass, t.MeteringGroup, t.Env))
	}

	return handlers
}

// Helper functions for replay

func getMonthStartTimestamp(timestamp int64) int64 {
	t := time.Unix(timestamp, 0).UTC()
	firstDay := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.UTC)
	return firstDay.Unix()
}

func getMergeLocalFilePath(timestamp int64) string {
	return fmt.Sprintf("data/metering/%s/%d.sqlite", constants.StatisticMergeTaskName, timestamp)
}

func createReplayMergeReaderWriter(ctx context.Context, timestamp int64) iface.StatisticMergeReaderWriter {
	localFilePath := getMergeLocalFilePath(timestamp)

	// Ensure directory exists
	dir := filepath.Dir(localFilePath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		panic(err)
	}

	db, err := database.GetSqliteDB(localFilePath)
	if err != nil {
		panic(err)
	}

	return &database.GormRW{
		DB: db,
	}
}

func closeReplayReaderWriter(rw interface{}) {
	if gormRW, ok := rw.(*database.GormRW); ok {
		database.CloseSqliteDB(gormRW.DB)
	}
}

func saveRecordsToMergeDB(ctx context.Context, mergeRW iface.StatisticMergeReaderWriter, records []*models.StatisticRecord) error {
	if len(records) == 0 {
		return nil
	}
	return database.InsertRecordsToMergeDB(ctx, mergeRW, records)
}
