package game

import (
	"context"
	"fmt"
	"math"
	"math/rand"
	"sort"

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

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

// Point is a 2D coordinate on the world map.
type Point struct {
	X, Y float64
}

// Road connects two towns with a sequence of waypoints.
type Road struct {
	ID         int64
	FromTownID int64
	ToTownID   int64
	Waypoints  []Point // ordered: first = from-town center, last = to-town center
	Distance   float64
}

// TownNPC is a quest/shop NPC placed in a town (from npcs table).
type TownNPC struct {
	ID         int64
	Name       string
	Type       string
	BuildingID *int64
}

// TownBuilding is a building placed in a town (from town_buildings table).
type TownBuilding struct {
	ID           int64
	TownID       int64
	BuildingType string
	OffsetX      float64
	OffsetY      float64
	Facing       string
	FootprintW   float64
	FootprintH   float64
}

// RoadGraph is an immutable in-memory graph of all roads and towns,
// loaded once at startup.
type RoadGraph struct {
	Roads          map[int64]*Road            // road ID -> road
	TownRoads      map[int64][]*Road          // town ID -> outgoing roads
	Towns          map[int64]*model.Town      // town ID -> town
	TownOrder      []int64                    // ordered town IDs for sequential traversal
	TownNPCs       map[int64][]TownNPC        // town ID -> NPCs (stable order)
	NPCByID        map[int64]TownNPC          // NPC id -> row
	TownBuildings  map[int64][]TownBuilding   // town ID -> buildings
}

// LoadRoadGraph reads roads and towns from the database, generates waypoints
// deterministically, and returns an immutable RoadGraph.
func LoadRoadGraph(ctx context.Context, pool *pgxpool.Pool) (*RoadGraph, error) {
	g := &RoadGraph{
		Roads:         make(map[int64]*Road),
		TownRoads:     make(map[int64][]*Road),
		Towns:         make(map[int64]*model.Town),
		TownNPCs:      make(map[int64][]TownNPC),
		NPCByID:       make(map[int64]TownNPC),
		TownBuildings: make(map[int64][]TownBuilding),
	}

	// Load towns.
	rows, err := pool.Query(ctx, `SELECT id, name, biome, world_x, world_y, radius, level_min, level_max FROM towns ORDER BY level_min ASC`)
	if err != nil {
		return nil, fmt.Errorf("load towns: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var t model.Town
		if err := rows.Scan(&t.ID, &t.Name, &t.Biome, &t.WorldX, &t.WorldY, &t.Radius, &t.LevelMin, &t.LevelMax); err != nil {
			return nil, fmt.Errorf("scan town: %w", err)
		}
		g.Towns[t.ID] = &t
		g.TownOrder = append(g.TownOrder, t.ID)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate towns: %w", err)
	}

	npcRows, err := pool.Query(ctx, `SELECT id, town_id, name, type, building_id FROM npcs ORDER BY town_id, id`)
	if err != nil {
		return nil, fmt.Errorf("load npcs: %w", err)
	}
	defer npcRows.Close()
	for npcRows.Next() {
		var n TownNPC
		var townID int64
		if err := npcRows.Scan(&n.ID, &townID, &n.Name, &n.Type, &n.BuildingID); err != nil {
			return nil, fmt.Errorf("scan npc: %w", err)
		}
		g.NPCByID[n.ID] = n
		g.TownNPCs[townID] = append(g.TownNPCs[townID], n)
	}
	if err := npcRows.Err(); err != nil {
		return nil, fmt.Errorf("iterate npcs: %w", err)
	}

	// Load buildings.
	buildingRows, err := pool.Query(ctx, `SELECT id, town_id, building_type, offset_x, offset_y, facing, footprint_w, footprint_h FROM town_buildings ORDER BY town_id, id`)
	if err != nil {
		return nil, fmt.Errorf("load buildings: %w", err)
	}
	defer buildingRows.Close()
	for buildingRows.Next() {
		var b TownBuilding
		if err := buildingRows.Scan(&b.ID, &b.TownID, &b.BuildingType, &b.OffsetX, &b.OffsetY, &b.Facing, &b.FootprintW, &b.FootprintH); err != nil {
			return nil, fmt.Errorf("scan building: %w", err)
		}
		g.TownBuildings[b.TownID] = append(g.TownBuildings[b.TownID], b)
	}
	if err := buildingRows.Err(); err != nil {
		return nil, fmt.Errorf("iterate buildings: %w", err)
	}

	// Load roads.
	roadRows, err := pool.Query(ctx, `SELECT id, from_town_id, to_town_id, distance FROM roads`)
	if err != nil {
		return nil, fmt.Errorf("load roads: %w", err)
	}
	defer roadRows.Close()

	for roadRows.Next() {
		var r Road
		if err := roadRows.Scan(&r.ID, &r.FromTownID, &r.ToTownID, &r.Distance); err != nil {
			return nil, fmt.Errorf("scan road: %w", err)
		}

		fromTown, ok := g.Towns[r.FromTownID]
		if !ok {
			continue
		}
		toTown, ok := g.Towns[r.ToTownID]
		if !ok {
			continue
		}

		r.Waypoints = generateWaypoints(fromTown, toTown, r.ID)
		r.Distance = totalWaypointDistance(r.Waypoints)
		g.Roads[r.ID] = &r
		g.TownRoads[r.FromTownID] = append(g.TownRoads[r.FromTownID], &r)
	}
	if err := roadRows.Err(); err != nil {
		return nil, fmt.Errorf("iterate roads: %w", err)
	}

	return g, nil
}

// TownBuildingInfos returns building payloads for the given town, with absolute world coordinates.
func (g *RoadGraph) TownBuildingInfos(townID int64) []model.TownBuildingInfo {
	town := g.Towns[townID]
	if town == nil {
		return nil
	}
	buildings := g.TownBuildings[townID]
	infos := make([]model.TownBuildingInfo, 0, len(buildings))
	for _, b := range buildings {
		infos = append(infos, model.TownBuildingInfo{
			ID:           b.ID,
			BuildingType: b.BuildingType,
			WorldX:       town.WorldX + b.OffsetX,
			WorldY:       town.WorldY + b.OffsetY,
			Facing:       b.Facing,
			FootprintW:   b.FootprintW,
			FootprintH:   b.FootprintH,
		})
	}
	return infos
}

// TownNPCInfos returns NPC payloads for the given town, including building IDs.
func (g *RoadGraph) TownNPCInfos(townID int64) []model.TownNPCInfo {
	npcs := g.TownNPCs[townID]
	infos := make([]model.TownNPCInfo, 0, len(npcs))
	for _, n := range npcs {
		infos = append(infos, model.TownNPCInfo{
			ID:         n.ID,
			Name:       n.Name,
			Type:       n.Type,
			BuildingID: n.BuildingID,
		})
	}
	return infos
}

// TownNPCIDs returns NPC ids for a town in stable DB order (for visit queues).
func (g *RoadGraph) TownNPCIDs(townID int64) []int64 {
	list := g.TownNPCs[townID]
	ids := make([]int64, len(list))
	for i := range list {
		ids[i] = list[i].ID
	}
	return ids
}

// FindRoad returns the road from fromTownID to toTownID, if it exists.
func (g *RoadGraph) FindRoad(fromTownID, toTownID int64) *Road {
	for _, r := range g.TownRoads[fromTownID] {
		if r.ToTownID == toTownID {
			return r
		}
	}
	return nil
}

// NextTownInChain returns the next town along the ring (TownOrder order, wraps after last).
func (g *RoadGraph) NextTownInChain(currentTownID int64) int64 {
	n := len(g.TownOrder)
	if n == 0 {
		return currentTownID
	}
	idx := -1
	for i, id := range g.TownOrder {
		if id == currentTownID {
			idx = i
			break
		}
	}
	if idx < 0 {
		return g.TownOrder[0]
	}
	return g.TownOrder[(idx+1)%n]
}

// HeroInTownAt returns true if (x, y) lies inside any town's radius (vendor / sell zone).
func (g *RoadGraph) HeroInTownAt(x, y float64) bool {
	if g == nil {
		return false
	}
	for _, t := range g.Towns {
		if t == nil {
			continue
		}
		dx := x - t.WorldX
		dy := y - t.WorldY
		if dx*dx+dy*dy <= t.Radius*t.Radius {
			return true
		}
	}
	return false
}

// NearestTown returns the town ID closest to the given world position.
func (g *RoadGraph) NearestTown(x, y float64) int64 {
	bestDist := math.MaxFloat64
	var bestID int64
	for _, t := range g.Towns {
		d := math.Hypot(t.WorldX-x, t.WorldY-y)
		if d < bestDist {
			bestDist = d
			bestID = t.ID
		}
	}
	return bestID
}

// TownOrderIndex returns the index of a town in the ordered chain, or -1 if not found.
func (g *RoadGraph) TownOrderIndex(townID int64) int {
	for i, id := range g.TownOrder {
		if id == townID {
			return i
		}
	}
	return -1
}

const (
	waypointSpacing = 20.0 // tiles between jitter points
	waypointJitter  = 2.0  // +/- tile jitter
)

// generateWaypoints creates a deterministic sequence of points from one town center
// to another, with jitter applied every waypointSpacing tiles.
func generateWaypoints(from, to *model.Town, roadID int64) []Point {
	dx := to.WorldX - from.WorldX
	dy := to.WorldY - from.WorldY
	totalDist := math.Hypot(dx, dy)

	if totalDist < 1 {
		return []Point{{from.WorldX, from.WorldY}, {to.WorldX, to.WorldY}}
	}

	numSegments := int(totalDist / waypointSpacing)
	if numSegments < 1 {
		numSegments = 1
	}

	// Deterministic RNG per road.
	rng := rand.New(rand.NewSource(roadID * 7919))

	points := make([]Point, 0, numSegments+2)
	points = append(points, Point{from.WorldX, from.WorldY})

	for i := 1; i < numSegments; i++ {
		t := float64(i) / float64(numSegments)
		px := from.WorldX + dx*t + (rng.Float64()*2-1)*waypointJitter
		py := from.WorldY + dy*t + (rng.Float64()*2-1)*waypointJitter
		points = append(points, Point{px, py})
	}

	points = append(points, Point{to.WorldX, to.WorldY})
	return points
}

// totalWaypointDistance computes the sum of segment lengths along a waypoint path.
func totalWaypointDistance(pts []Point) float64 {
	var total float64
	for i := 1; i < len(pts); i++ {
		total += math.Hypot(pts[i].X-pts[i-1].X, pts[i].Y-pts[i-1].Y)
	}
	return total
}

// SortedTownsByDistance returns town IDs sorted by Euclidean distance from (x,y).
func (g *RoadGraph) SortedTownsByDistance(x, y float64) []int64 {
	type td struct {
		id   int64
		dist float64
	}
	tds := make([]td, 0, len(g.Towns))
	for _, t := range g.Towns {
		tds = append(tds, td{id: t.ID, dist: math.Hypot(t.WorldX-x, t.WorldY-y)})
	}
	sort.Slice(tds, func(i, j int) bool { return tds[i].dist < tds[j].dist })
	ids := make([]int64, len(tds))
	for i, v := range tds {
		ids[i] = v.id
	}
	return ids
}
