package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/denisovdennis/autohero/internal/game"
	"github.com/denisovdennis/autohero/internal/model"
	"github.com/denisovdennis/autohero/internal/storage"
)

func main() {
	var (
		listHeroes  = flag.Bool("list-heroes", false, "list heroes from DB and exit (use -filter for name; numeric filter matches id/telegram)")
		listEnemies = flag.Bool("list-enemies", false, "list enemy archetypes from DB and exit")
		filter      = flag.String("filter", "", "optional substring filter for -list-heroes / -list-enemies")
		listLimit   = flag.Int("limit", 50, "max rows for -list-heroes / -list-enemies")

		heroID     = flag.Int64("hero-id", 0, "existing hero id in DB (optional)")
		heroLevel  = flag.Int("hero-level", 1, "reference hero level when hero-id is not provided")
		enemyType  = flag.String("enemy-type", "", "enemy archetype type (required)")
		enemyLevel = flag.Int("enemy-level", 0, "enemy instance level (0 = catalog midpoint (min_level+max_level)/2 for this archetype)")
		iterations = flag.Int("iterations", 50, "number of simulation runs")
		seed       = flag.Int64("seed", time.Now().UnixNano(), "rng seed")
		delayMs    = flag.Int64("delay-ms", 0, "wall-clock delay between simulation events (0 = instant)")
	)
	flag.Parse()

	dsn := os.Getenv("DATABASE_URL")
	if dsn == "" {
		log.Fatal("DATABASE_URL is required")
	}
	ctx := context.Background()
	pool, err := pgxpool.New(ctx, dsn)
	if err != nil {
		log.Fatalf("open db: %v", err)
	}
	defer pool.Close()

	cs := storage.NewContentStore(pool)
	templates, err := cs.LoadEnemyTemplates(ctx)
	if err != nil {
		log.Fatalf("load enemies: %v", err)
	}
	model.SetEnemyTemplates(templates)

	if *listHeroes {
		if *listLimit <= 0 || *listLimit > 200 {
			log.Fatal("limit must be 1..200")
		}
		hs := storage.NewHeroStore(pool, nil)
		heroes, err := hs.ListHeroesFiltered(ctx, *listLimit, 0, *filter)
		if err != nil {
			log.Fatalf("list heroes: %v", err)
		}
		fmt.Printf("# heroes (filter=%q) count=%d\n", *filter, len(heroes))
		fmt.Printf("%-10s %-36s %6s %10s %10s\n", "id", "name", "level", "telegramId", "state")
		for _, h := range heroes {
			fmt.Printf("%-10d %-36s %6d %10d %10s\n", h.ID, trimName(h.Name), h.Level, h.TelegramID, h.State)
		}
		return
	}

	if *listEnemies {
		if *listLimit <= 0 || *listLimit > 500 {
			log.Fatal("limit must be 1..500")
		}
		type row struct {
			slug string
			name string
			tmpl model.Enemy
		}
		var rows []row
		for _, e := range templates {
			rows = append(rows, row{slug: e.Slug, name: e.Name, tmpl: e})
		}
		sort.Slice(rows, func(i, j int) bool { return rows[i].slug < rows[j].slug })
		f := strings.TrimSpace(strings.ToLower(*filter))
		fmt.Printf("# enemy archetypes from DB (filter=%q)\n", *filter)
		fmt.Printf("%-22s %-32s %6s %6s %6s %5s\n", "type (-enemy-type)", "name", "minLv", "maxLv", "baseLv", "elite")
		printed := 0
		for _, r := range rows {
			if f != "" {
				if !strings.Contains(strings.ToLower(r.slug), f) &&
					!strings.Contains(strings.ToLower(r.name), f) {
					continue
				}
			}
			if printed >= *listLimit {
				break
			}
			e := r.tmpl
			fmt.Printf("%-22s %-32s %6d %6d %6d %5v\n",
				r.slug, trimName(r.name), e.MinLevel, e.MaxLevel, e.BaseLevel, e.IsElite)
			printed++
		}
		if f != "" {
			fmt.Printf("# printed %d rows (limit=%d)\n", printed, *listLimit)
		}
		return
	}

	if *enemyType == "" {
		log.Fatal("enemy-type is required (or use -list-heroes / -list-enemies)")
	}
	if *iterations <= 0 {
		log.Fatal("iterations must be > 0")
	}

	tmpl, ok := model.EnemyBySlug(*enemyType)
	if !ok {
		log.Fatalf("enemy type not found: %s", *enemyType)
	}

	var baseHero *model.Hero
	if *heroID > 0 {
		hs := storage.NewHeroStore(pool, nil)
		h, getErr := hs.GetByID(ctx, *heroID)
		if getErr != nil {
			log.Fatalf("load hero by id: %v", getErr)
		}
		if h == nil {
			log.Fatalf("hero not found: %d", *heroID)
		}
		baseHero = h
	} else {
		baseHero = game.NewReferenceHeroForBalance(*heroLevel, game.ReferenceGearMedian, nil)
	}

	heroLv := *heroLevel
	if *heroID > 0 {
		heroLv = baseHero.Level
	}
	instanceLv := *enemyLevel
	if instanceLv <= 0 {
		instanceLv = defaultSimEnemyLevel(tmpl)
	}

	rand.Seed(*seed)

	var wins int
	var total time.Duration
	durations := make([]time.Duration, 0, *iterations)

	for i := 0; i < *iterations; i++ {
		hero := game.CloneHeroForCombatSim(baseHero)
		if hero.HP <= 0 {
			hero.HP = hero.MaxHP
		}
		enemy := game.BuildEnemyInstanceForLevel(tmpl, instanceLv)
		survived, elapsed := game.ResolveCombatToEndWithDuration(hero, &enemy, game.CombatSimDeterministicStart, game.CombatSimOptions{
			TickRate:       100 * time.Millisecond,
			WallClockDelay: time.Duration(*delayMs) * time.Millisecond,
			MaxSteps:       game.CombatSimMaxStepsLong,
		})
		if survived {
			wins++
		}
		total += elapsed
		durations = append(durations, elapsed)
	}

	sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] })
	median := durations[len(durations)/2]
	fmt.Printf("heroLevel=%d enemyInstanceLevel=%d enemy=%s iterations=%d wins=%d winRate=%.2f%%\n",
		heroLv, instanceLv, *enemyType, *iterations, wins, 100*float64(wins)/float64(*iterations))
	fmt.Printf("mean=%s median=%s wallDelayMs=%d\n", (time.Duration(int64(total) / int64(*iterations))).String(), median.String(), *delayMs)
}

// defaultSimEnemyLevel picks a representative level for balance sim when -enemy-level is omitted:
// midpoint of the archetype level band from DB (e.g. Lightning Titan 25–35 → 30).
func defaultSimEnemyLevel(t model.Enemy) int {
	if t.MinLevel > 0 && t.MaxLevel >= t.MinLevel {
		return (t.MinLevel + t.MaxLevel) / 2
	}
	if t.BaseLevel > 0 {
		return t.BaseLevel
	}
	return 1
}

func trimName(s string) string {
	const max = 34
	s = strings.TrimSpace(s)
	runes := []rune(s)
	if len(runes) <= max {
		return s
	}
	return string(runes[:max-1]) + "…"
}
