package maintenance

import (
	"context"
	"log"
	"os"
	"path/filepath"
	"sync"
	"time"

	"crypto-miner-server/internal/db"
)

// retentionTickInterval is the delay between scheduled retention passes (overridable in tests).
var retentionTickInterval = 6 * time.Hour

// runRetentionFn is the work function invoked by StartRetentionJobs (overridable in tests).
var runRetentionFn = runRetention

var (
	retentionMu     sync.Mutex
	retentionCancel context.CancelFunc
)

// StartRetentionJobs purges old stats and build artifacts on an interval.
func StartRetentionJobs(database *db.Database, dataDir string, statsHours, buildDays int) {
	if statsHours <= 0 && buildDays <= 0 {
		return
	}
	ctx, cancel := context.WithCancel(context.Background())
	retentionMu.Lock()
	retentionCancel = cancel
	retentionMu.Unlock()

	go func() {
		runRetentionFn(database, dataDir, statsHours, buildDays)
		ticker := time.NewTicker(retentionTickInterval)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				runRetentionFn(database, dataDir, statsHours, buildDays)
			}
		}
	}()
}

// StopRetentionJobs stops the background retention loop started by StartRetentionJobs.
func StopRetentionJobs() {
	retentionMu.Lock()
	cancel := retentionCancel
	retentionCancel = nil
	retentionMu.Unlock()
	if cancel != nil {
		cancel()
	}
}

func runRetention(database *db.Database, dataDir string, statsHours, buildDays int) {
	if statsHours > 0 {
		cutoff := time.Now().Add(-time.Duration(statsHours) * time.Hour)
		n, err := database.PurgeHashrateSamplesBefore(cutoff)
		if err != nil {
			log.Printf("[Retention] hashrate purge failed: %v", err)
		} else if n > 0 {
			log.Printf("[Retention] purged %d hashrate samples older than %dh", n, statsHours)
		}
	}
	if buildDays > 0 {
		cutoff := time.Now().Add(-time.Duration(buildDays) * 24 * time.Hour)
		builds, err := database.ListBuildsOlderThan(cutoff)
		if err != nil {
			log.Printf("[Retention] build list failed: %v", err)
			return
		}
		for _, b := range builds {
			// Delete the DB record first.  If this fails the build directory is
			// still intact so the next retention pass can retry cleanly.
			if err := database.DeleteBuild(b.ID); err != nil {
				log.Printf("[Retention] delete build %s from DB: %v — skipping file removal", b.ID, err)
				continue
			}
			// Remove files only after the DB row is gone.
			var dir string
			if b.FilePath != "" {
				dir = filepath.Dir(b.FilePath)
			} else {
				dir = filepath.Join(dataDir, "builds", b.ID)
			}
			if err := os.RemoveAll(dir); err != nil {
				log.Printf("[Retention] remove build dir %s: %v", dir, err)
			} else {
				log.Printf("[Retention] removed build %s (%s)", b.ID, b.WorkerName)
			}
		}
	}
}
