package model

import (
	"context"
	"encoding/json"
	"log/slog"
	"sync/atomic"
	"time"
)

// BuffJSON is DB/admin JSON for one buff type (durations in ms).
type BuffJSON struct {
	Name       string  `json:"name"`
	DurationMs int64   `json:"durationMs"`
	Magnitude  float64 `json:"magnitude"`
	CooldownMs int64   `json:"cooldownMs"`
}

// DebuffJSON is DB/admin JSON for one debuff type (duration in ms).
type DebuffJSON struct {
	Name       string  `json:"name"`
	DurationMs int64   `json:"durationMs"`
	Magnitude  float64 `json:"magnitude"`
}

type buffDebuffPayload struct {
	Buffs   map[string]BuffJSON   `json:"buffs"`
	Debuffs map[string]DebuffJSON `json:"debuffs"`
}

type buffDebuffCatalogData struct {
	buffs   map[BuffType]Buff
	debuffs map[DebuffType]Debuff
}

var buffDebuffCatalog atomic.Value

func init() {
	buffDebuffCatalog.Store(&buffDebuffCatalogData{
		buffs:   seedBuffMap(),
		debuffs: seedDebuffMap(),
	})
}

func seedBuffMap() map[BuffType]Buff {
	return map[BuffType]Buff{
		BuffRush: {
			Type: BuffRush, Name: "Rush",
			Duration: 5 * time.Minute, Magnitude: 0.30,
			CooldownDuration: 15 * time.Minute,
		},
		BuffRage: {
			Type: BuffRage, Name: "Rage",
			Duration: 3 * time.Minute, Magnitude: 0.50,
			CooldownDuration: 10 * time.Minute,
		},
		BuffShield: {
			Type: BuffShield, Name: "Shield",
			Duration: 5 * time.Minute, Magnitude: 0.35,
			CooldownDuration: 12 * time.Minute,
		},
		BuffLuck: {
			Type: BuffLuck, Name: "Luck",
			Duration: 30 * time.Minute, Magnitude: 1.0,
			CooldownDuration: 2 * time.Hour,
		},
		BuffResurrection: {
			Type: BuffResurrection, Name: "Resurrection",
			Duration: 10 * time.Minute, Magnitude: 0.40,
			CooldownDuration: 30 * time.Minute,
		},
		BuffHeal: {
			Type: BuffHeal, Name: "Heal",
			Duration: 1 * time.Second, Magnitude: 0.35,
			CooldownDuration: 5 * time.Minute,
		},
		BuffPowerPotion: {
			Type: BuffPowerPotion, Name: "Power Potion",
			Duration: 5 * time.Minute, Magnitude: 0.85,
			CooldownDuration: 20 * time.Minute,
		},
		BuffWarCry: {
			Type: BuffWarCry, Name: "War Cry",
			Duration: 3 * time.Minute, Magnitude: 0.50,
			CooldownDuration: 10 * time.Minute,
		},
	}
}

func seedDebuffMap() map[DebuffType]Debuff {
	return map[DebuffType]Debuff{
		DebuffPoison: {
			Type: DebuffPoison, Name: "Poison",
			Duration: 5 * time.Second, Magnitude: 0.02,
		},
		DebuffFreeze: {
			Type: DebuffFreeze, Name: "Freeze",
			Duration: 3 * time.Second, Magnitude: 0.50,
		},
		DebuffBurn: {
			Type: DebuffBurn, Name: "Burn",
			Duration: 4 * time.Second, Magnitude: 0.03,
		},
		DebuffStun: {
			Type: DebuffStun, Name: "Stun",
			Duration: 2 * time.Second, Magnitude: 1.0,
		},
		DebuffSlow: {
			Type: DebuffSlow, Name: "Slow",
			Duration: 4 * time.Second, Magnitude: 0.40,
		},
		DebuffWeaken: {
			Type: DebuffWeaken, Name: "Weaken",
			Duration: 5 * time.Second, Magnitude: 0.30,
		},
		DebuffIceSlow: {
			Type: DebuffIceSlow, Name: "Ice Slow",
			Duration: 4 * time.Second, Magnitude: 0.20,
		},
	}
}

func buffFromStrictJSON(bt BuffType, j BuffJSON) Buff {
	return Buff{
		Type:             bt,
		Name:             j.Name,
		Duration:         time.Duration(j.DurationMs) * time.Millisecond,
		Magnitude:        j.Magnitude,
		CooldownDuration: time.Duration(j.CooldownMs) * time.Millisecond,
	}
}

func debuffFromStrictJSON(dt DebuffType, j DebuffJSON) Debuff {
	return Debuff{
		Type:      dt,
		Name:      j.Name,
		Duration:  time.Duration(j.DurationMs) * time.Millisecond,
		Magnitude: j.Magnitude,
	}
}

func cloneBuffMap(src map[BuffType]Buff) map[BuffType]Buff {
	out := make(map[BuffType]Buff, len(src))
	for k, v := range src {
		out[k] = v
	}
	return out
}

func cloneDebuffMap(src map[DebuffType]Debuff) map[DebuffType]Debuff {
	out := make(map[DebuffType]Debuff, len(src))
	for k, v := range src {
		out[k] = v
	}
	return out
}

// BuffDebuffPayloadLoader loads raw JSON from persistence.
type BuffDebuffPayloadLoader interface {
	LoadBuffDebuffConfigPayload(ctx context.Context) ([]byte, error)
}

// ReloadBuffDebuffCatalog merges DB payload into built-in seeds (same pattern as tuning).
func ReloadBuffDebuffCatalog(ctx context.Context, logger *slog.Logger, loader BuffDebuffPayloadLoader) error {
	payload, err := loader.LoadBuffDebuffConfigPayload(ctx)
	if err != nil {
		if logger != nil {
			logger.Warn("buff/debuff config load failed", "error", err)
		}
		return err
	}
	buffs := cloneBuffMap(seedBuffMap())
	debuffs := cloneDebuffMap(seedDebuffMap())

	if len(payload) > 0 {
		var raw buffDebuffPayload
		if err := json.Unmarshal(payload, &raw); err != nil {
			if logger != nil {
				logger.Warn("buff/debuff config parse failed", "error", err)
			}
			return err
		}
		// Per-key full replace: payload must include all fields for edited types (admin UI sends full effective maps).
		for key, j := range raw.Buffs {
			bt := BuffType(key)
			if _, ok := buffs[bt]; ok {
				buffs[bt] = buffFromStrictJSON(bt, j)
			}
		}
		for key, j := range raw.Debuffs {
			dt := DebuffType(key)
			if _, ok := debuffs[dt]; ok {
				debuffs[dt] = debuffFromStrictJSON(dt, j)
			}
		}
	}

	buffDebuffCatalog.Store(&buffDebuffCatalogData{buffs: buffs, debuffs: debuffs})
	return nil
}

func catalogData() *buffDebuffCatalogData {
	return buffDebuffCatalog.Load().(*buffDebuffCatalogData)
}

// BuffDefinition returns the active buff template (DB + defaults).
func BuffDefinition(bt BuffType) (Buff, bool) {
	b, ok := catalogData().buffs[bt]
	return b, ok
}

// DebuffDefinition returns the active debuff template (DB + defaults).
func DebuffDefinition(dt DebuffType) (Debuff, bool) {
	d, ok := catalogData().debuffs[dt]
	return d, ok
}

// BuffCatalogSnapshot returns copies for admin/API.
func BuffCatalogSnapshot() map[BuffType]Buff {
	src := catalogData().buffs
	out := make(map[BuffType]Buff, len(src))
	for k, v := range src {
		out[k] = v
	}
	return out
}

// DebuffCatalogSnapshot returns copies for admin/API.
func DebuffCatalogSnapshot() map[DebuffType]Debuff {
	src := catalogData().debuffs
	out := make(map[DebuffType]Debuff, len(src))
	for k, v := range src {
		out[k] = v
	}
	return out
}

// BuffToJSON converts Buff to BuffJSON (ms).
func BuffToJSON(b Buff) BuffJSON {
	return BuffJSON{
		Name:       b.Name,
		DurationMs: b.Duration.Milliseconds(),
		Magnitude:  b.Magnitude,
		CooldownMs: b.CooldownDuration.Milliseconds(),
	}
}

// DebuffToJSON converts Debuff to DebuffJSON (ms).
func DebuffToJSON(d Debuff) DebuffJSON {
	return DebuffJSON{
		Name:       d.Name,
		DurationMs: d.Duration.Milliseconds(),
		Magnitude:  d.Magnitude,
	}
}

// BuffCatalogEffectiveJSON builds string-keyed maps for admin/API.
func BuffCatalogEffectiveJSON() (map[string]BuffJSON, map[string]DebuffJSON) {
	buffs := BuffCatalogSnapshot()
	outB := make(map[string]BuffJSON, len(buffs))
	for t, b := range buffs {
		outB[string(t)] = BuffToJSON(b)
	}
	debuffs := DebuffCatalogSnapshot()
	outD := make(map[string]DebuffJSON, len(debuffs))
	for t, d := range debuffs {
		outD[string(t)] = DebuffToJSON(d)
	}
	return outB, outD
}
