package web

import (
	"encoding/json"
	"strings"
	"testing"

	"pete/internal/storage"
	"time"
)

// fogMap is the shape gogobee sends: two visited rooms (entry, elite), and two
// frontier rooms reachable one hop out — n4 is really the boss but arrives with
// its kind withheld as "unknown". n3 sits behind a perception-locked door, n4
// behind a key-locked one.
func fogMap() *whoMap {
	return &whoMap{
		ZoneID:      "ossuary",
		CurrentNode: "n2",
		Visited:     []string{"n1", "n2"},
		Nodes: []whoMapNode{
			{ID: "n1", Kind: "entry"},
			{ID: "n2", Kind: "elite"},
			{ID: "n3", Kind: "unknown"},
			{ID: "n4", Kind: "unknown"},
		},
		Edges: []whoMapEdge{
			{From: "n1", To: "n2", Lock: ""},
			{From: "n1", To: "n3", Lock: "perception_check"},
			{From: "n2", To: "n4", Lock: "key_required"},
		},
	}
}

func TestBuildMapView_Layout(t *testing.T) {
	v := buildMapView(fogMap())
	if v == nil {
		t.Fatal("nil view for a non-empty map")
	}
	if len(v.Nodes) != 4 {
		t.Fatalf("want 4 nodes, got %d", len(v.Nodes))
	}

	// Depth places entry in column 0, its two successors in column 1, the boss
	// frontier in column 2 — a left-to-right dungeon.
	var n1, n2, n4 mapNode
	for _, n := range v.Nodes {
		switch {
		case n.Kind == "entry":
			n1 = n
		case n.Kind == "elite":
			n2 = n
		case n.Kind == "unknown" && n.X > n1.X+mapColGap:
			n4 = n // the deepest unknown is n4
		}
	}
	if !(n1.X < n2.X && n2.X < n4.X) {
		t.Errorf("columns not left-to-right: entry=%d elite=%d frontier=%d", n1.X, n2.X, n4.X)
	}
	// The boss frontier must NOT reveal its kind or its glyph.
	if n4.Kind != "unknown" || n4.Glyph != "?" {
		t.Errorf("frontier boss leaked: kind=%q glyph=%q", n4.Kind, n4.Glyph)
	}
	if n2.Current != true {
		t.Errorf("current node n2 (elite) should be marked current")
	}
	if n1.Current {
		t.Errorf("entry is not the current node")
	}

	// Edges: three total, the two locked ones dashed, the visited-visited one open.
	if len(v.Edges) != 3 {
		t.Fatalf("want 3 edges, got %d", len(v.Edges))
	}
	locked := 0
	for _, e := range v.Edges {
		if e.Locked {
			locked++
		}
	}
	if locked != 2 {
		t.Errorf("want 2 locked doors (perception, key), got %d", locked)
	}
}

func TestBuildMapView_EmptyIsNil(t *testing.T) {
	if buildMapView(nil) != nil {
		t.Error("nil map should give nil view")
	}
	if buildMapView(&whoMap{ZoneID: "x"}) != nil {
		t.Error("map with no nodes should give nil view")
	}
}

// publicDetailWithMap is publicDetail plus a fog-of-war map, the way the roster
// push carries both.
func publicDetailWithMap(t *testing.T) json.RawMessage {
	t.Helper()
	raw, err := json.Marshal(map[string]any{
		"hp_current":  30,
		"hp_max":      42,
		"armor_class": 17,
		"abilities":   [6]int{16, 14, 15, 10, 12, 8},
		"modifiers":   [6]int{3, 2, 2, 0, 1, -1},
		"supplies":    8,
		"room":        "2 / 4",
		"map":         fogMap(),
	})
	if err != nil {
		t.Fatal(err)
	}
	return raw
}

// TestWhoMapRenders drives the real template with a map present and asserts the
// SVG lands, the current room is marked, and the fog holds: the frontier boss
// is drawn as an unexplored room, never as a boss.
func TestWhoMapRenders(t *testing.T) {
	s, _ := newAdvServer(t, "tok")
	s.auth = &Authenticator{secret: []byte("test-secret-key-at-least-16")}
	now := time.Now().Unix()

	e := entry("tok-josie", "Josie", "expedition", "holymachina")
	e.Detail = publicDetailWithMap(t)
	if w := postRoster(t, s, "tok", rosterPush{SnapshotAt: now, Adventurers: []storage.RosterEntry{e}}); w.Code != 200 {
		t.Fatalf("seed roster = %d", w.Code)
	}

	w := getWho(t, s, "tok-josie", "")
	if w.Code != 200 {
		t.Fatalf("who = %d: %s", w.Code, w.Body.String())
	}
	body := w.Body.String()

	for _, want := range []string{"The map", "map-svg", "map-node-current", "map-edge-locked"} {
		if !strings.Contains(body, want) {
			t.Errorf("map render missing %q", want)
		}
	}
	// The elite room glyph shows (visited); the boss glyph must not (the boss is
	// only reachable as an unrevealed frontier door).
	if !strings.Contains(body, "◆") {
		t.Error("visited elite room should show its glyph")
	}
	if strings.Contains(body, "♛") {
		t.Error("fog leak: the boss glyph rendered for an unexplored frontier room")
	}
}
