package steamvdf

import "testing"

// Realistic ACF as steamcmd writes it for a 7DTD dedicated server pinned
// to a frozen beta branch.
const acf7dtd = `"AppState"
{
	"appid"		"294420"
	"Universe"		"1"
	"name"		"7 Days to Die Dedicated Server"
	"StateFlags"		"4"
	"installdir"		"7 Days to Die Dedicated Server"
	"LastUpdated"		"1750912345"
	"SizeOnDisk"		"14680064000"
	"StagingSize"		"0"
	"buildid"		"14503775"
	"LastOwner"		"76561190000000000"
	"UpdateResult"		"0"
	"BytesToDownload"		"0"
	"BytesDownloaded"		"0"
	"AutoUpdateBehavior"		"0"
	"AllowOtherDownloadsWhileRunning"		"0"
	"ScheduledAutoUpdate"		"0"
	"InstalledDepots"
	{
		"294422"
		{
			"manifest"		"1118115119020227469"
			"size"		"14680064000"
		}
	}
	"UserConfig"
	{
		"betakey"		"v2.6"
	}
	"MountedConfig"
	{
		"betakey"		"v2.6"
	}
}
`

const acfPublic = `"AppState"
{
	"appid"		"2430930"
	"name"		"Palworld Dedicated Server"
	"buildid"		"20112233"
	"UserConfig"
	{
	}
}
`

func TestParseAppManifestBetaBranch(t *testing.T) {
	m, err := ParseAppManifest(acf7dtd)
	if err != nil {
		t.Fatalf("ParseAppManifest: %v", err)
	}
	if m.AppID != "294420" || m.BuildID != "14503775" || m.BetaKey != "v2.6" {
		t.Fatalf("got %+v", m)
	}
	if m.Name != "7 Days to Die Dedicated Server" {
		t.Fatalf("name: %q", m.Name)
	}
}

func TestParseAppManifestPublic(t *testing.T) {
	m, err := ParseAppManifest(acfPublic)
	if err != nil {
		t.Fatalf("ParseAppManifest: %v", err)
	}
	if m.BuildID != "20112233" || m.BetaKey != "" {
		t.Fatalf("got %+v", m)
	}
}

func TestParseAppManifestNoBuildID(t *testing.T) {
	if _, err := ParseAppManifest(`"AppState" { "appid" "1" }`); err == nil {
		t.Fatal("expected error for manifest without buildid")
	}
}

// app_info_print output as steamcmd emits it: banner noise, the numeric
// appid root key, escaped quotes, nested branches with pwdrequired etc.
const appInfo = `Redirecting stderr to '/root/Steam/logs/stderr.txt'
Loading Steam API...OK
Connecting anonymously to Steam Public...OK
Waiting for client config...OK
Waiting for user info...OK
AppID : 294420, change number : 27581234/0, last change : Sat Jun 28 12:00:00 2026
"294420"
{
	"common"
	{
		"name"		"7 Days to Die Dedicated Server"
		"type"		"Tool"
	}
	"config"
	{
		"installdir"		"7 Days to Die Dedicated Server"
	}
	"depots"
	{
		"294422"
		{
			"name"		"Linux Depot"
			"config"
			{
				"oslist"		"linux"
			}
		}
		"branches"
		{
			"public"
			{
				"buildid"		"19216811"
				"timeupdated"		"1751168000"
			}
			"latest_experimental"
			{
				"buildid"		"19333444"
				"description"		"Latest \"experimental\" build"
				"timeupdated"		"1752168000"
			}
			"v2.6"
			{
				"buildid"		"14503775"
				"description"		"2.6 Stable"
				"timeupdated"		"1750168000"
			}
			"private_qa"
			{
				"buildid"		"19999999"
				"pwdrequired"		"1"
			}
		}
	}
}
`

func TestParseAppInfoBranches(t *testing.T) {
	b, err := ParseAppInfoBranches(appInfo, "294420")
	if err != nil {
		t.Fatalf("ParseAppInfoBranches: %v", err)
	}
	want := map[string]string{
		"public":              "19216811",
		"latest_experimental": "19333444",
		"v2.6":                "14503775",
		"private_qa":          "19999999",
	}
	for k, v := range want {
		if b[k] != v {
			t.Fatalf("branch %s: got %q want %q (all: %v)", k, b[k], v, b)
		}
	}
}

func TestParseAppInfoBranchesUnknownRootKey(t *testing.T) {
	// Caller passes the wrong appid; fallback finds the object anyway.
	b, err := ParseAppInfoBranches(appInfo, "999999")
	if err != nil {
		t.Fatalf("fallback failed: %v", err)
	}
	if b["public"] != "19216811" {
		t.Fatalf("got %v", b)
	}
}

func TestParseAppInfoNoBranches(t *testing.T) {
	if _, err := ParseAppInfoBranches(`"1" { "common" { "name" "x" } }`, "1"); err == nil {
		t.Fatal("expected error when branches missing")
	}
}

func TestParseToleratesCommentsAndUnquoted(t *testing.T) {
	kv, err := Parse("// comment\nAppState { key value // trailing\n nested { a b } }")
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if kv.Get("AppState", "key") != "value" || kv.Get("appstate", "nested", "a") != "b" {
		t.Fatalf("got %v", kv)
	}
}

func TestParseUnterminated(t *testing.T) {
	if _, err := Parse(`"A" { "k" "v"`); err == nil {
		t.Fatal("expected unterminated-object error")
	}
}
