package sandbox

import (
	"testing"
)

// TestInitConsistency: every option resolves an enum + a default index, and no
// two options share an enum (the 2-char code must be unique per option).
func TestInitConsistency(t *testing.T) {
	if len(options) != 150 {
		t.Fatalf("expected 150 sandbox options, got %d", len(options))
	}
	seen := map[int]string{}
	for i := range options {
		o := &options[i]
		if o.enum == 0 && o.Name != "RangedDamage" {
			t.Errorf("%s: enum unresolved (0)", o.Name)
		}
		if prev, ok := seen[o.enum]; ok {
			t.Errorf("enum %d shared by %s and %s", o.enum, prev, o.Name)
		}
		seen[o.enum] = o.Name
		if _, ok := defaultIdx[o.Name]; !ok {
			t.Errorf("%s: no default index", o.Name)
		}
	}
}

// TestGameDefaultCode decodes the game's own default difficulty (Adventurer)
// code from serverconfig.xml and checks the documented meaning, then re-encodes
// and confirms it round-trips byte-for-byte.
func TestGameDefaultCode(t *testing.T) {
	const code = "AAAJABJACJADJARFBNC" // shipped in 3.0 serverconfig.xml comment
	c := V3Codec{}
	vals, err := c.Decode(code)
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	// Triples: AA J / AB J / AC J / AD J / AR F / BN C
	//   AA(0)=RangedDamage  idx J=9  -> DamageValues[9]=1.5
	//   AB(1)=MeleeDamage   idx 9   -> 1.5
	//   AC(2)=BlockDamage   idx 9   -> 1.5
	//   AD(3)=TerrainDamage idx 9   -> 1.5
	//   AR(17)=IncomingDamage idx F=5 -> DamageValues[5]=0.75
	//   BN(39)=AISmellMode  idx C=2 -> AISmellMode set Ints[2]=2
	checks := map[string]string{
		"RangedDamage":   "1.5",
		"MeleeDamage":    "1.5",
		"BlockDamage":    "1.5",
		"TerrainDamage":  "1.5",
		"IncomingDamage": "0.75",
		"AISmellMode":    "2",
	}
	for name, want := range checks {
		if got := vals[name]; got != want {
			t.Errorf("%s = %q, want %q", name, got, want)
		}
	}
	// Re-encode: only the 6 non-default options should appear, in enum order →
	// must reproduce the original code exactly.
	round, err := c.Encode(vals)
	if err != nil {
		t.Fatalf("encode: %v", err)
	}
	if round != code {
		t.Errorf("round-trip mismatch:\n have %q\n want %q", round, code)
	}
}

// TestEmptyAndDefaultsEncodeEmpty: the all-defaults map encodes to just the
// version char (no triples), and an empty code decodes to all defaults.
func TestEmptyAndDefaultsEncodeEmpty(t *testing.T) {
	c := V3Codec{}
	def := DefaultsMap()
	code, err := c.Encode(def)
	if err != nil {
		t.Fatalf("encode defaults: %v", err)
	}
	if code != "A" {
		t.Errorf("defaults should encode to bare version char %q, got %q", "A", code)
	}
	vals, err := c.Decode("")
	if err != nil {
		t.Fatalf("decode empty: %v", err)
	}
	if len(vals) != 150 {
		t.Errorf("empty decode should yield 150 defaults, got %d", len(vals))
	}
	for name, v := range vals {
		if def[name] != v {
			t.Errorf("%s: empty-decode %q != defaults %q", name, v, def[name])
		}
	}
}

// TestRoundTripNonDefault sets a spread of options across categories/types and
// confirms encode→decode preserves them.
func TestRoundTripNonDefault(t *testing.T) {
	c := V3Codec{}
	in := DefaultsMap()
	in["BloodMoonFrequency"] = "14" // int set, non-default
	in["XPMultiplier"] = "2"        // float set
	in["AllowZombieDigging"] = "false"
	in["TraderResetInterval"] = "7"
	in["SillyBigHeads"] = "true"
	code, err := c.Encode(in)
	if err != nil {
		t.Fatalf("encode: %v", err)
	}
	out, err := c.Decode(code)
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	for _, k := range []string{"BloodMoonFrequency", "XPMultiplier", "AllowZombieDigging", "TraderResetInterval", "SillyBigHeads"} {
		if out[k] != in[k] {
			t.Errorf("%s: round-trip %q != %q", k, out[k], in[k])
		}
	}
}

// TestAlphaHelpers spot-checks the index<->alpha conversions against the C#.
func TestAlphaHelpers(t *testing.T) {
	if v, _ := alpha2ToIndex("BN"); v != 39 {
		t.Errorf("BN -> %d, want 39", v)
	}
	if v, _ := alpha2ToIndex("AA"); v != 0 {
		t.Errorf("AA -> %d, want 0", v)
	}
	if s, _ := indexToAlpha2(39); s != "BN" {
		t.Errorf("39 -> %q, want BN", s)
	}
	if v, _ := alphaToIndex('J'); v != 9 {
		t.Errorf("J -> %d, want 9", v)
	}
	if b, _ := indexToAlpha(9); b != 'J' {
		t.Errorf("9 -> %c, want J", b)
	}
}

// TestValidate rejects malformed codes and accepts good ones.
func TestValidate(t *testing.T) {
	if err := Validate(""); err != nil {
		t.Errorf("empty should be valid: %v", err)
	}
	if err := Validate("AAAJABJACJADJARFBNC"); err != nil {
		t.Errorf("game default should be valid: %v", err)
	}
	if err := Validate("ZZZ"); err == nil {
		t.Error("wrong version char should fail")
	}
	if err := Validate("AAB"); err == nil {
		t.Error("non-multiple-of-3 body should fail")
	}
}
