package plugin

import (
	"testing"

	"maunium.net/go/mautrix/id"
)

func TestRegionsForZone_OnlyMultiRegion(t *testing.T) {
	for _, z := range []ZoneID{ZoneUnderdark, ZoneDragonsLair, ZoneAbyssPortal} {
		rs := regionsForZone(z)
		if len(rs) != 4 {
			t.Errorf("zone %s: got %d regions, want 4", z, len(rs))
		}
		if !IsMultiRegionZone(z) {
			t.Errorf("IsMultiRegionZone(%s) = false", z)
		}
		// Verify last region marks IsZoneBoss; first does not.
		if rs[0].IsZoneBoss {
			t.Errorf("zone %s: first region marked IsZoneBoss", z)
		}
		if !rs[len(rs)-1].IsZoneBoss {
			t.Errorf("zone %s: last region not marked IsZoneBoss", z)
		}
		// Verify Order is 1..N.
		for i, r := range rs {
			if r.Order != i+1 {
				t.Errorf("zone %s: regions[%d].Order = %d", z, i, r.Order)
			}
			if r.ZoneID != z {
				t.Errorf("zone %s: region %s has ZoneID %s", z, r.ID, r.ZoneID)
			}
		}
	}
	// Non-multi-region zone returns nil.
	if rs := regionsForZone(ZoneSunkenTemple); rs != nil {
		t.Errorf("ZoneSunkenTemple regions = %v, want nil", rs)
	}
	if IsMultiRegionZone(ZoneSunkenTemple) {
		t.Error("ZoneSunkenTemple should not be multi-region")
	}
}

func TestRegionLookups(t *testing.T) {
	if r, ok := firstRegion(ZoneUnderdark); !ok || r.ID != "underdark_surface_tunnels" {
		t.Errorf("firstRegion underdark = %+v / %v", r, ok)
	}
	if r, ok := nextRegion(ZoneUnderdark, "underdark_surface_tunnels"); !ok || r.ID != "underdark_drow_outpost" {
		t.Errorf("nextRegion = %+v / %v", r, ok)
	}
	if _, ok := nextRegion(ZoneUnderdark, "underdark_deep_throne"); ok {
		t.Error("nextRegion past last should be false")
	}
	if _, ok := findRegion(ZoneUnderdark, "no_such_region"); ok {
		t.Error("findRegion of missing id returned ok")
	}
}

func TestCurrentRegion_DefaultsToFirst(t *testing.T) {
	setupZoneRunTestDB(t)
	uid := id.UserID("@region-cur:example.org")
	defer cleanupExpeditions(uid)

	exp, err := startExpedition(uid, ZoneUnderdark, "", ExpeditionSupplies{Max: 10, Current: 10, DailyBurn: 1})
	if err != nil {
		t.Fatal(err)
	}
	r, ok := CurrentRegion(exp)
	if !ok {
		t.Fatal("CurrentRegion returned !ok")
	}
	if r.ID != "underdark_surface_tunnels" {
		t.Errorf("CurrentRegion default = %s, want underdark_surface_tunnels", r.ID)
	}

	// Single-region zone returns false.
	cleanupExpeditions(uid)
	exp2, err := startExpedition(uid, ZoneSunkenTemple, "", ExpeditionSupplies{Max: 10, Current: 10, DailyBurn: 1})
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := CurrentRegion(exp2); ok {
		t.Error("CurrentRegion on single-region zone returned ok")
	}
}

func TestMarkRegionBossDefeated_FlipsZoneBoss(t *testing.T) {
	setupZoneRunTestDB(t)
	uid := id.UserID("@region-boss:example.org")
	defer cleanupExpeditions(uid)

	exp, err := startExpedition(uid, ZoneUnderdark, "", ExpeditionSupplies{Max: 10, Current: 10, DailyBurn: 1})
	if err != nil {
		t.Fatal(err)
	}

	// Non-zone-boss region: clears region, leaves zone BossDefeated.
	if changed, err := MarkRegionBossDefeated(exp, "underdark_drow_outpost"); err != nil || !changed {
		t.Fatalf("first mark: changed=%v err=%v", changed, err)
	}
	if !IsRegionCleared(exp, "underdark_drow_outpost") {
		t.Error("region not cleared after Mark")
	}
	if exp.BossDefeated {
		t.Error("BossDefeated flipped on non-zone-boss region")
	}

	// Idempotent.
	if changed, _ := MarkRegionBossDefeated(exp, "underdark_drow_outpost"); changed {
		t.Error("idempotent mark reported change")
	}

	// Zone-boss region: flips BossDefeated and persists.
	if changed, err := MarkRegionBossDefeated(exp, "underdark_deep_throne"); err != nil || !changed {
		t.Fatalf("zone-boss mark: changed=%v err=%v", changed, err)
	}
	if !exp.BossDefeated {
		t.Error("BossDefeated not set after zone-boss kill")
	}
	loaded, _ := getActiveExpedition(uid)
	if !loaded.BossDefeated {
		t.Error("BossDefeated did not persist")
	}
	if !IsRegionCleared(loaded, "underdark_deep_throne") {
		t.Error("zone-boss region not in cleared list after reload")
	}
}

func TestStartExpedition_DefaultsCurrentRegionForMultiRegionZone(t *testing.T) {
	setupZoneRunTestDB(t)
	uid := id.UserID("@region-default:example.org")
	defer cleanupExpeditions(uid)

	exp, err := startExpedition(uid, ZoneAbyssPortal, "", ExpeditionSupplies{Max: 10, Current: 10, DailyBurn: 1})
	if err != nil {
		t.Fatal(err)
	}
	if exp.CurrentRegion != "abyss_outer_rift" {
		t.Errorf("CurrentRegion = %q, want abyss_outer_rift", exp.CurrentRegion)
	}
	if !IsRegionVisited(exp, "abyss_outer_rift") {
		t.Error("entry region not in visited list")
	}
	loaded, _ := getActiveExpedition(uid)
	if loaded.CurrentRegion != "abyss_outer_rift" {
		t.Errorf("reload CurrentRegion = %q", loaded.CurrentRegion)
	}
}

func TestRegionStateLists_PersistAndRoundTrip(t *testing.T) {
	setupZoneRunTestDB(t)
	uid := id.UserID("@region-state:example.org")
	defer cleanupExpeditions(uid)

	exp, err := startExpedition(uid, ZoneUnderdark, "", ExpeditionSupplies{Max: 10, Current: 10, DailyBurn: 1})
	if err != nil {
		t.Fatal(err)
	}

	if changed, err := addRegionListEntry(exp, regionStateClearedKey, "underdark_surface_tunnels"); err != nil || !changed {
		t.Fatalf("first add: changed=%v err=%v", changed, err)
	}
	if changed, err := addRegionListEntry(exp, regionStateClearedKey, "underdark_surface_tunnels"); err != nil || changed {
		t.Errorf("duplicate add: changed=%v err=%v (want false/nil)", changed, err)
	}
	if !IsRegionCleared(exp, "underdark_surface_tunnels") {
		t.Error("IsRegionCleared after add = false")
	}

	// Round-trip through DB.
	loaded, err := getActiveExpedition(uid)
	if err != nil || loaded == nil {
		t.Fatalf("reload: %v / %v", loaded, err)
	}
	if !IsRegionCleared(loaded, "underdark_surface_tunnels") {
		t.Error("IsRegionCleared after reload = false (state did not persist)")
	}
	if IsRegionCleared(loaded, "underdark_drow_outpost") {
		t.Error("IsRegionCleared for unwritten region = true")
	}
	if HasBaseCampAt(loaded, "underdark_drow_outpost") {
		t.Error("HasBaseCampAt unset = true")
	}
}
