package main

import (
	"bssapp-backend/queries"
	"context"
	"database/sql"
	"log"
	"os"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

func startProductPerformanceScheduler(pgDB *sql.DB) {
	enabled := strings.TrimSpace(strings.ToLower(os.Getenv("PRODUCT_PERFORMANCE_ENABLED")))
	if enabled == "0" || enabled == "false" || enabled == "off" {
		log.Println("Product performance scheduler disabled")
		return
	}
	if pgDB == nil {
		return
	}

	deltaDays := productPerformanceEnvInt("PRODUCT_PERFORMANCE_DELTA_DAYS", 45, 1, 365)
	deltaHHMM := productPerformanceEnvString("PRODUCT_PERFORMANCE_DELTA_HHMM", "02:00")
	deltaTimeoutHours := productPerformanceEnvInt("PRODUCT_PERFORMANCE_DELTA_TIMEOUT_HOURS", 6, 1, 24)
	runOnStartup := productPerformanceEnvBool("PRODUCT_PERFORMANCE_DELTA_RUN_ON_STARTUP", false)
	fullEnabled := productPerformanceEnvBool("PRODUCT_PERFORMANCE_FULL_ENABLED", false)
	fullWeekday := productPerformanceEnvWeekday("PRODUCT_PERFORMANCE_FULL_WEEKDAY", time.Saturday)
	fullHHMM := productPerformanceEnvString("PRODUCT_PERFORMANCE_FULL_HHMM", "16:00")
	fullStartDate := productPerformanceEnvDate("PRODUCT_PERFORMANCE_FULL_START_DATE", time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local))
	fullTimeoutHours := productPerformanceEnvInt("PRODUCT_PERFORMANCE_FULL_TIMEOUT_HOURS", 18, 1, 72)
	fullRunOnStartup := productPerformanceEnvBool("PRODUCT_PERFORMANCE_FULL_RUN_ON_STARTUP", false)

	var running int32
	runDelta := func(reason string) {
		if !atomic.CompareAndSwapInt32(&running, 0, 1) {
			log.Printf("[ProductPerformanceJob] skip (%s): already running", reason)
			return
		}
		defer atomic.StoreInt32(&running, 0)

		endDate := time.Now()
		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deltaTimeoutHours)*time.Hour)
		defer cancel()

		result, err := queries.RefreshProductPerformance(ctx, pgDB, queries.ProductPerformanceRefreshRequest{
			Mode:      "delta",
			Stage:     "all",
			StartDate: endDate.AddDate(0, 0, -deltaDays),
			EndDate:   endDate,
		})
		if err != nil {
			log.Printf("[ProductPerformanceJob] error (%s): %v", reason, err)
			return
		}
		log.Printf("[ProductPerformanceJob] ok (%s): mode=%s stage=%s start=%s end=%s sales=%d stock=%d kpi=%d snapshots=%d duration_ms=%d",
			reason, result.Mode, result.Stage, result.StartDate, result.EndDate, result.SalesRows, result.StockRows, result.KpiRows, result.SnapshotRows, result.DurationMS)
	}
	runFull := func(reason string) {
		if !atomic.CompareAndSwapInt32(&running, 0, 1) {
			log.Printf("[ProductPerformanceJob] skip (%s): already running", reason)
			return
		}
		defer atomic.StoreInt32(&running, 0)

		endDate := time.Now()
		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(fullTimeoutHours)*time.Hour)
		defer cancel()

		result, err := queries.RefreshProductPerformance(ctx, pgDB, queries.ProductPerformanceRefreshRequest{
			Mode:      "full",
			Stage:     "all",
			StartDate: fullStartDate,
			EndDate:   endDate,
		})
		if err != nil {
			log.Printf("[ProductPerformanceJob] error (%s): %v", reason, err)
			return
		}
		log.Printf("[ProductPerformanceJob] ok (%s): mode=%s stage=%s start=%s end=%s sales=%d stock=%d kpi=%d snapshots=%d duration_ms=%d",
			reason, result.Mode, result.Stage, result.StartDate, result.EndDate, result.SalesRows, result.StockRows, result.KpiRows, result.SnapshotRows, result.DurationMS)
	}

	log.Printf("[ProductPerformanceJob] scheduled daily_delta=%s lookback_days=%d run_delta_on_startup=%t",
		deltaHHMM, deltaDays, runOnStartup)
	if fullEnabled {
		log.Printf("[ProductPerformanceJob] scheduled weekly_full=%s %s start_date=%s timeout_hours=%d run_full_on_startup=%t",
			fullWeekday.String(), fullHHMM, fullStartDate.Format("2006-01-02"), fullTimeoutHours, fullRunOnStartup)
	}

	go func() {
		if runOnStartup {
			time.Sleep(20 * time.Second)
			runDelta("startup-delta")
		}

		for {
			next := productPerformanceNextDaily(time.Now(), deltaHHMM)
			log.Printf("[ProductPerformanceJob] daily delta next_at=%s in=%s", next.Format(time.RFC3339), time.Until(next).Round(time.Second))
			time.Sleep(time.Until(next))
			runDelta("daily-delta")
		}
	}()
	if fullEnabled {
		go func() {
			if fullRunOnStartup {
				time.Sleep(30 * time.Second)
				runFull("startup-full")
			}

			for {
				next := productPerformanceNextWeekly(time.Now(), fullWeekday, fullHHMM)
				log.Printf("[ProductPerformanceJob] weekly full next_at=%s in=%s", next.Format(time.RFC3339), time.Until(next).Round(time.Second))
				time.Sleep(time.Until(next))
				runFull("weekly-full")
			}
		}()
	}
}

func productPerformanceNextDaily(now time.Time, hhmm string) time.Time {
	hour, minute := productPerformanceParseHHMM(hhmm, 2, 0)
	next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location())
	if !next.After(now) {
		next = next.AddDate(0, 0, 1)
	}
	return next
}

func productPerformanceNextWeekly(now time.Time, weekday time.Weekday, hhmm string) time.Time {
	hour, minute := productPerformanceParseHHMM(hhmm, 16, 0)
	daysUntil := (int(weekday) - int(now.Weekday()) + 7) % 7
	next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location()).AddDate(0, 0, daysUntil)
	if !next.After(now) {
		next = next.AddDate(0, 0, 7)
	}
	return next
}

func productPerformanceParseHHMM(raw string, fallbackHour, fallbackMinute int) (int, int) {
	parts := strings.Split(strings.TrimSpace(raw), ":")
	if len(parts) != 2 {
		return fallbackHour, fallbackMinute
	}
	h, herr := strconv.Atoi(parts[0])
	m, merr := strconv.Atoi(parts[1])
	if herr != nil || merr != nil || h < 0 || h > 23 || m < 0 || m > 59 {
		return fallbackHour, fallbackMinute
	}
	return h, m
}

func productPerformanceEnvString(name, fallback string) string {
	raw := strings.TrimSpace(os.Getenv(name))
	if raw == "" {
		return fallback
	}
	return raw
}

func productPerformanceEnvInt(name string, fallback, min, max int) int {
	raw := strings.TrimSpace(os.Getenv(name))
	if raw == "" {
		return fallback
	}
	n, err := strconv.Atoi(raw)
	if err != nil || n < min || n > max {
		return fallback
	}
	return n
}

func productPerformanceEnvBool(name string, fallback bool) bool {
	raw := strings.TrimSpace(strings.ToLower(os.Getenv(name)))
	if raw == "" {
		return fallback
	}
	return raw == "1" || raw == "true" || raw == "on" || raw == "yes"
}

func productPerformanceEnvDate(name string, fallback time.Time) time.Time {
	raw := strings.TrimSpace(os.Getenv(name))
	if raw == "" {
		return fallback
	}
	parsed, err := time.ParseInLocation("2006-01-02", raw, time.Local)
	if err != nil {
		return fallback
	}
	return parsed
}

func productPerformanceEnvWeekday(name string, fallback time.Weekday) time.Weekday {
	switch strings.ToLower(strings.TrimSpace(os.Getenv(name))) {
	case "sunday", "sun", "pazar", "0":
		return time.Sunday
	case "monday", "mon", "pazartesi", "1":
		return time.Monday
	case "tuesday", "tue", "sali", "salı", "2":
		return time.Tuesday
	case "wednesday", "wed", "carsamba", "çarşamba", "3":
		return time.Wednesday
	case "thursday", "thu", "persembe", "perşembe", "4":
		return time.Thursday
	case "friday", "fri", "cuma", "5":
		return time.Friday
	case "saturday", "sat", "cumartesi", "6":
		return time.Saturday
	default:
		return fallback
	}
}
