package model

import (
	"fmt"
	"time"

	"github.com/denisovdennis/autohero/internal/tuning"
)

// BuffFreeChargesPerType defines per-buff charge limits per BuffChargePeriod (default 24h).
var BuffFreeChargesPerType = map[BuffType]int{
	BuffRush:         2,
	BuffRage:         1,
	BuffShield:       1,
	BuffLuck:         1,
	BuffResurrection: 1,
	BuffHeal:         2,
	BuffPowerPotion:  1,
	BuffWarCry:       1,
}

// BuffSubscriberChargesPerType defines higher per-buff caps for active subscribers (not a flat 2x).
var BuffSubscriberChargesPerType = map[BuffType]int{
	BuffRush:         3,
	BuffRage:         2,
	BuffShield:       2,
	BuffLuck:         2,
	BuffResurrection: 2,
	BuffHeal:         3,
	BuffPowerPotion:  1,
	BuffWarCry:       2,
}

func SubscriptionWeeklyPrice() int64 {
	return tuning.Get().SubscriptionWeeklyPriceRUB
}

func SubscriptionDurationRuntime() time.Duration {
	return time.Duration(tuning.Get().SubscriptionDurationMs) * time.Millisecond
}

func SubscriptionDurationLabel() string {
	d := SubscriptionDurationRuntime()
	if d <= 0 {
		return "0 hours"
	}
	if d%(24*time.Hour) == 0 {
		days := int64(d / (24 * time.Hour))
		if days == 1 {
			return "1 day"
		}
		return fmt.Sprintf("%d days", days)
	}
	hours := int64(d / time.Hour)
	if hours == 1 {
		return "1 hour"
	}
	return fmt.Sprintf("%d hours", hours)
}

func BuffChargePeriod() time.Duration {
	return time.Duration(tuning.Get().BuffChargePeriodMs) * time.Millisecond
}

func FreeBuffActivationsPerPeriodRuntime() int {
	return int(tuning.Get().FreeBuffActivationsPerPeriod)
}

// RefreshSubscription checks if the subscription has expired and updates SubscriptionActive.
// Returns true if the hero state was changed (caller should persist).
func (h *Hero) RefreshSubscription(now time.Time) bool {
	if !h.SubscriptionActive {
		return false
	}
	if h.SubscriptionExpiresAt != nil && now.After(*h.SubscriptionExpiresAt) {
		h.SubscriptionActive = false
		h.SubscriptionExpiresAt = nil
		return true
	}
	return false
}

// ActivateSubscription sets the hero as a subscriber for SubscriptionDuration.
// If already subscribed, extends from current expiry.
func (h *Hero) ActivateSubscription(now time.Time) {
	if h.SubscriptionActive && h.SubscriptionExpiresAt != nil && h.SubscriptionExpiresAt.After(now) {
		// Extend from current expiry.
		extended := h.SubscriptionExpiresAt.Add(SubscriptionDurationRuntime())
		h.SubscriptionExpiresAt = &extended
	} else {
		expires := now.Add(SubscriptionDurationRuntime())
		h.SubscriptionExpiresAt = &expires
	}
	h.SubscriptionActive = true
}

// MaxBuffCharges returns the max charges for a buff type, considering subscription status.
func (h *Hero) MaxBuffCharges(bt BuffType) int {
	if h.SubscriptionActive {
		if v, ok := BuffSubscriberChargesPerType[bt]; ok {
			return v
		}
	}
	if v, ok := BuffFreeChargesPerType[bt]; ok {
		return v
	}
	return FreeBuffActivationsPerPeriodRuntime()
}

// MaxRevives returns the max free revives per period (1 free, 2 for subscribers).
func (h *Hero) MaxRevives() int {
	if h.SubscriptionActive {
		return int(tuning.Get().MaxRevivesSubscriber)
	}
	return int(tuning.Get().MaxRevivesFree)
}

// ApplyBuffQuotaRollover refills free buff charges when the 24h window has passed.
// Returns true if the hero was mutated (caller may persist).
// Deprecated: kept for backward compat with the shared counter. New code should
// use GetBuffCharges / ConsumeBuffCharge which handle rollover inline.
func (h *Hero) ApplyBuffQuotaRollover(now time.Time) bool {
	if h.SubscriptionActive {
		return false
	}
	if h.BuffQuotaPeriodEnd == nil {
		return false
	}
	changed := false
	for now.After(*h.BuffQuotaPeriodEnd) {
		h.BuffFreeChargesRemaining = FreeBuffActivationsPerPeriodRuntime()
		next := h.BuffQuotaPeriodEnd.Add(BuffChargePeriod())
		h.BuffQuotaPeriodEnd = &next
		changed = true
	}
	return changed
}

// GetBuffCharges returns the current charge state for a specific buff type,
// rolling over the 24h window if expired.
func (h *Hero) GetBuffCharges(bt BuffType, now time.Time) BuffChargeState {
	if h.BuffCharges == nil {
		h.BuffCharges = make(map[string]BuffChargeState)
	}

	maxCharges := h.MaxBuffCharges(bt)

	state, exists := h.BuffCharges[string(bt)]
	if !exists {
		// First access for this buff type — initialize with full charges.
		pe := now.Add(BuffChargePeriod())
		state = BuffChargeState{
			Remaining: maxCharges,
			PeriodEnd: &pe,
		}
		h.BuffCharges[string(bt)] = state
		return state
	}

	// Roll over if the period has expired.
	if state.PeriodEnd != nil && now.After(*state.PeriodEnd) {
		for state.PeriodEnd != nil && now.After(*state.PeriodEnd) {
			next := state.PeriodEnd.Add(BuffChargePeriod())
			state.PeriodEnd = &next
		}
		state.Remaining = maxCharges
		h.BuffCharges[string(bt)] = state
	}

	return state
}

// ConsumeBuffCharge decrements one free charge for the specific buff type.
// Returns an error if no charges remain.
func (h *Hero) ConsumeBuffCharge(bt BuffType, now time.Time) error {
	state := h.GetBuffCharges(bt, now)

	if state.Remaining <= 0 {
		periodStr := "unknown"
		if state.PeriodEnd != nil {
			periodStr = state.PeriodEnd.Format(time.RFC3339)
		}
		return fmt.Errorf(
			"no free %s charges left; next refresh at %s",
			string(bt), periodStr,
		)
	}

	state.Remaining--
	h.BuffCharges[string(bt)] = state

	// Keep legacy counter roughly in sync.
	h.BuffFreeChargesRemaining--
	if h.BuffFreeChargesRemaining < 0 {
		h.BuffFreeChargesRemaining = 0
	}

	return nil
}

// RefundBuffCharge increments one charge back for the specific buff type.
func (h *Hero) RefundBuffCharge(bt BuffType) {
	if h.BuffCharges == nil {
		return
	}
	state, exists := h.BuffCharges[string(bt)]
	if !exists {
		return
	}
	maxCap := h.MaxBuffCharges(bt)
	state.Remaining++
	if state.Remaining > maxCap {
		state.Remaining = maxCap
	}
	h.BuffCharges[string(bt)] = state

	// Keep legacy counter roughly in sync.
	h.BuffFreeChargesRemaining++
}

// ResetBuffCharges resets charges to max. If bt is nil, resets ALL buff types.
// If bt is non-nil, resets only that buff type.
func (h *Hero) ResetBuffCharges(bt *BuffType, now time.Time) {
	if h.BuffCharges == nil {
		h.BuffCharges = make(map[string]BuffChargeState)
	}

	pe := now.Add(BuffChargePeriod())

	if bt != nil {
		maxCharges := h.MaxBuffCharges(*bt)
		h.BuffCharges[string(*bt)] = BuffChargeState{
			Remaining: maxCharges,
			PeriodEnd: &pe,
		}
		return
	}

	// Reset ALL buff types.
	for buffType := range BuffFreeChargesPerType {
		h.BuffCharges[string(buffType)] = BuffChargeState{
			Remaining: h.MaxBuffCharges(buffType),
			PeriodEnd: &pe,
		}
	}

	// Also reset legacy counter.
	h.BuffFreeChargesRemaining = FreeBuffActivationsPerPeriodRuntime()
	h.BuffQuotaPeriodEnd = &pe
}

// EnsureBuffChargesPopulated initializes BuffCharges for all buff types if the map
// is empty. Returns true if the map was freshly populated (caller should persist).
func (h *Hero) EnsureBuffChargesPopulated(now time.Time) bool {
	if h.BuffCharges == nil {
		h.BuffCharges = make(map[string]BuffChargeState)
	}
	if len(h.BuffCharges) == 0 {
		pe := now.Add(BuffChargePeriod())
		if h.BuffQuotaPeriodEnd != nil {
			pe = *h.BuffQuotaPeriodEnd
		}
		for bt := range BuffFreeChargesPerType {
			h.BuffCharges[string(bt)] = BuffChargeState{
				Remaining: h.MaxBuffCharges(bt),
				PeriodEnd: &pe,
			}
		}
		return true
	}
	return false
}
