package controller

import (
	"fmt"
	"math/rand/v2"
	"slices"

	"galaxy/game/internal/generator"
	"galaxy/game/internal/model/game"

	"github.com/google/uuid"
)

func NewGame(r Repo, races []string) (uuid.UUID, error) {
	m, err := generator.Generate(func(ms *generator.MapSetting) {
		ms.Players = uint32(len(races))
	})
	if err != nil {
		return uuid.Nil, fmt.Errorf("generate map: %s", err)
	}
	return newGameOnMap(r, races, m)
}

func newGameOnMap(r Repo, races []string, m generator.Map) (uuid.UUID, error) {
	g, err := buildGameOnMap(races, m)
	if err != nil {
		return uuid.Nil, err
	}
	if err := r.SaveNewTurn(0, g); err != nil {
		return uuid.Nil, err
	}
	c := NewCache(g)
	for rep := range c.Report(c.g.Turn, nil, nil) {
		if err := r.SaveReport(c.g.Turn, rep); err != nil {
			return uuid.Nil, err
		}
	}
	return g.ID, nil
}

func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) {
	if len(races) != len(m.HomePlanets) {
		return nil, fmt.Errorf("generate map: wrong number of home planets: %d, expected: %d ", len(m.HomePlanets), len(races))
	}
	gameID, err := uuid.NewRandom()
	if err != nil {
		return nil, fmt.Errorf("generate game uuid: %s", err)
	}
	g := &game.Game{
		ID:   gameID,
		Turn: 0,
		Race: make([]game.Race, len(races)),
	}
	gameMap := &game.Map{
		Width:  m.Width,
		Height: m.Height,
		Planet: make([]game.Planet, 0),
	}
	var planetCount uint = 0
	relations := make([]game.RaceRelation, len(races))
	for i := range races {
		raceID, err := uuid.NewRandom()
		if err != nil {
			return nil, fmt.Errorf("generate race uuid: %s", err)
		}
		relations[i] = game.RaceRelation{RaceID: raceID, Relation: game.RelationWar}
		g.Race[i] = game.Race{
			ID:      raceID,
			Name:    races[i],
			VoteFor: raceID,
			TTL:     10,
			Tech:    game.NewTechSet(),
		}
		gameMap.Planet = append(gameMap.Planet, NewPlanet(
			planetCount,
			m.HomePlanets[i].HW.RandomName(),
			&raceID,
			m.HomePlanets[i].HW.Position.X,
			m.HomePlanets[i].HW.Position.Y,
			m.HomePlanets[i].HW.Size,
			m.HomePlanets[i].HW.Size, // HW's pop & ind = size
			m.HomePlanets[i].HW.Size,
			m.HomePlanets[i].HW.Resources,
			game.ResearchDrive.AsType(uuid.Nil),
		))
		planetCount++
		for dw := range m.HomePlanets[i].DW {
			gameMap.Planet = append(gameMap.Planet, NewPlanet(
				planetCount,
				m.HomePlanets[i].DW[dw].RandomName(),
				&raceID,
				m.HomePlanets[i].DW[dw].Position.X,
				m.HomePlanets[i].DW[dw].Position.Y,
				m.HomePlanets[i].DW[dw].Size,
				m.HomePlanets[i].DW[dw].Size, // DW's pop & ind = size
				m.HomePlanets[i].DW[dw].Size,
				m.HomePlanets[i].DW[dw].Resources,
				game.ResearchDrive.AsType(uuid.Nil),
			))
			planetCount++
		}
	}
	for i := range g.Race {
		rel := slices.Clone(relations)
		selfIdx := slices.IndexFunc(rel, func(a game.RaceRelation) bool { return a.RaceID == g.Race[i].ID })
		g.Race[i].Relations = append(rel[:selfIdx], rel[selfIdx+1:]...)
	}

	for i := range m.FreePlanets {
		gameMap.Planet = append(gameMap.Planet, NewPlanet(
			planetCount,
			m.FreePlanets[i].RandomName(),
			&uuid.Nil,
			m.FreePlanets[i].Position.X,
			m.FreePlanets[i].Position.Y,
			m.FreePlanets[i].Size,
			0,
			0,
			m.FreePlanets[i].Resources,
			game.ProductionNone.AsType(uuid.Nil),
		))
		planetCount++
	}

	rand.Shuffle(len(gameMap.Planet), func(i, j int) {
		gameMap.Planet[i].Number, gameMap.Planet[j].Number = gameMap.Planet[j].Number, gameMap.Planet[i].Number
	})

	for i := range gameMap.Planet {
		g.Votes = g.Votes.Add(gameMap.Planet[i].Votes())
	}

	g.Map = *gameMap

	return g, nil
}

func NewPlanet(num uint, name string, owner *uuid.UUID, x, y, size, pop, ind, res float64, prod game.Production) game.Planet {
	if owner != nil && *owner == uuid.Nil {
		owner = nil
	}
	return game.Planet{
		Owner:      owner,
		X:          game.F(x),
		Y:          game.F(y),
		Number:     num,
		Size:       game.F(size),
		Name:       name,
		Resources:  game.F(res),
		Population: game.F(pop),
		Industry:   game.F(ind),
		Production: prod,
	}
}
