// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package config

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"testing"

	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/require"

	consensusconfig "github.com/luxfi/consensus/config"
	"github.com/luxfi/constants"
	"github.com/luxfi/ids"
	"github.com/luxfi/node/chains"
	"github.com/luxfi/node/genesis/builder"
	"github.com/luxfi/node/nets"
	pchaingenesis "github.com/luxfi/node/vms/platformvm/genesis"
	pchaintxs "github.com/luxfi/node/vms/platformvm/txs"
)

const chainConfigFilenameExtension = ".ex"

func TestGetChainConfigsFromFiles(t *testing.T) {
	tests := map[string]struct {
		configs  map[string]string
		upgrades map[string]string
		expected map[string]chains.ChainConfig
	}{
		"no chain configs": {
			configs:  map[string]string{},
			upgrades: map[string]string{},
			expected: map[string]chains.ChainConfig{},
		},
		"valid chain-id": {
			configs:  map[string]string{"yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp": "hello", "2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm": "world"},
			upgrades: map[string]string{"yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp": "helloUpgrades"},
			expected: func() map[string]chains.ChainConfig {
				m := map[string]chains.ChainConfig{}
				id1, err := ids.FromString("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp")
				require.NoError(t, err)
				m[id1.String()] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("helloUpgrades")}

				id2, err := ids.FromString("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm")
				require.NoError(t, err)
				m[id2.String()] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}

				return m
			}(),
		},
		"valid alias": {
			configs:  map[string]string{"C": "hello", "X": "world"},
			upgrades: map[string]string{"C": "upgradess"},
			expected: func() map[string]chains.ChainConfig {
				m := map[string]chains.ChainConfig{}
				m["C"] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("upgradess")}
				m["X"] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}

				return m
			}(),
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			require := require.New(t)
			root := t.TempDir()
			configJSON := fmt.Sprintf(`{%q: %q}`, ChainConfigDirKey, root)
			configFile := setupConfigJSON(t, root, configJSON)
			chainsDir := root
			// Create custom configs
			for key, value := range test.configs {
				chainDir := filepath.Join(chainsDir, key)
				setupFile(t, chainDir, chainConfigFileName+chainConfigFilenameExtension, value)
			}
			for key, value := range test.upgrades {
				chainDir := filepath.Join(chainsDir, key)
				setupFile(t, chainDir, chainUpgradeFileName+chainConfigFilenameExtension, value)
			}

			v := setupViper(configFile)

			// Parse config
			require.Equal(root, v.GetString(ChainConfigDirKey))
			chainConfigs, err := getChainConfigs(v)
			require.NoError(err)
			require.Equal(test.expected, chainConfigs)
		})
	}
}

func TestGetChainConfigsDirNotExist(t *testing.T) {
	tests := map[string]struct {
		structure   string
		file        map[string]string
		expectedErr error
		expected    map[string]chains.ChainConfig
	}{
		"cdir not exist": {
			structure:   "/",
			file:        map[string]string{"config.ex": "noeffect"},
			expectedErr: errCannotReadDirectory,
			expected:    nil,
		},
		"cdir is file ": {
			structure:   "/",
			file:        map[string]string{"cdir": "noeffect"},
			expectedErr: errCannotReadDirectory,
			expected:    nil,
		},
		"chain subdir not exist": {
			structure:   "/cdir/",
			file:        map[string]string{"config.ex": "noeffect"},
			expectedErr: nil,
			expected:    map[string]chains.ChainConfig{},
		},
		"full structure": {
			structure:   "/cdir/C/",
			file:        map[string]string{"config.ex": "hello"},
			expectedErr: nil,
			expected:    map[string]chains.ChainConfig{"C": {Config: []byte("hello"), Upgrade: []byte(nil)}},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			require := require.New(t)
			root := t.TempDir()
			chainConfigDir := filepath.Join(root, "cdir")
			configJSON := fmt.Sprintf(`{%q: %q}`, ChainConfigDirKey, chainConfigDir)
			configFile := setupConfigJSON(t, root, configJSON)

			dirToCreate := filepath.Join(root, test.structure)
			require.NoError(os.MkdirAll(dirToCreate, 0o700))

			for key, value := range test.file {
				setupFile(t, dirToCreate, key, value)
			}
			v := setupViper(configFile)

			// Parse config
			require.Equal(chainConfigDir, v.GetString(ChainConfigDirKey))

			// don't read with getConfigFromViper since it's very slow.
			chainConfigs, err := getChainConfigs(v)
			require.ErrorIs(err, test.expectedErr)
			require.Equal(test.expected, chainConfigs)
		})
	}
}

func TestSetChainConfigDefaultDir(t *testing.T) {
	require := require.New(t)
	root := t.TempDir()
	// changes internal package variable, since using defaultDir (under user home) is risky.
	defaultChainConfigDir = filepath.Join(root, "cdir")
	configFilePath := setupConfigJSON(t, root, "{}")

	v := setupViper(configFilePath)
	require.Equal(defaultChainConfigDir, v.GetString(ChainConfigDirKey))

	chainsDir := filepath.Join(defaultChainConfigDir, "C")
	setupFile(t, chainsDir, chainConfigFileName+chainConfigFilenameExtension, "helloworld")
	chainConfigs, err := getChainConfigs(v)
	require.NoError(err)
	expected := map[string]chains.ChainConfig{"C": {Config: []byte("helloworld"), Upgrade: []byte(nil)}}
	require.Equal(expected, chainConfigs)
}

func TestGetChainConfigsFromFlags(t *testing.T) {
	tests := map[string]struct {
		fullConfigs map[string]chains.ChainConfig
		expected    map[string]chains.ChainConfig
	}{
		"no chain configs": {
			fullConfigs: map[string]chains.ChainConfig{},
			expected:    map[string]chains.ChainConfig{},
		},
		"valid chain-id": {
			fullConfigs: func() map[string]chains.ChainConfig {
				m := map[string]chains.ChainConfig{}
				id1, err := ids.FromString("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp")
				require.NoError(t, err)
				m[id1.String()] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("helloUpgrades")}

				id2, err := ids.FromString("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm")
				require.NoError(t, err)
				m[id2.String()] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}

				return m
			}(),
			expected: func() map[string]chains.ChainConfig {
				m := map[string]chains.ChainConfig{}
				id1, err := ids.FromString("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp")
				require.NoError(t, err)
				m[id1.String()] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("helloUpgrades")}

				id2, err := ids.FromString("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm")
				require.NoError(t, err)
				m[id2.String()] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}

				return m
			}(),
		},
		"valid alias": {
			fullConfigs: map[string]chains.ChainConfig{
				"C": {Config: []byte("hello"), Upgrade: []byte("upgradess")},
				"X": {Config: []byte("world"), Upgrade: []byte(nil)},
			},
			expected: func() map[string]chains.ChainConfig {
				m := map[string]chains.ChainConfig{}
				m["C"] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("upgradess")}
				m["X"] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}

				return m
			}(),
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			require := require.New(t)
			jsonMaps, err := json.Marshal(test.fullConfigs)
			require.NoError(err)
			encodedFileContent := base64.StdEncoding.EncodeToString(jsonMaps)

			// build viper config
			v := setupViperFlags()
			v.Set(ChainConfigContentKey, encodedFileContent)

			// Parse config
			chainConfigs, err := getChainConfigs(v)
			require.NoError(err)
			require.Equal(test.expected, chainConfigs)
		})
	}
}

func TestGetVMAliasesFromFile(t *testing.T) {
	tests := map[string]struct {
		givenJSON   string
		expected    map[ids.ID][]string
		expectedErr error
	}{
		"wrong vm id": {
			givenJSON:   `{"wrongVmId": ["vm1","vm2"]}`,
			expected:    nil,
			expectedErr: errUnmarshalling,
		},
		"vm id": {
			givenJSON: `{"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": ["vm1","vm2"],
										"Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU": ["vm3", "vm4"] }`,
			expected: func() map[ids.ID][]string {
				m := map[ids.ID][]string{}
				id1, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
				id2, _ := ids.FromString("Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU")
				m[id1] = []string{"vm1", "vm2"}
				m[id2] = []string{"vm3", "vm4"}
				return m
			}(),
			expectedErr: nil,
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			require := require.New(t)
			root := t.TempDir()
			aliasPath := filepath.Join(root, "aliases.json")
			configJSON := fmt.Sprintf(`{%q: %q}`, VMAliasesFileKey, aliasPath)
			configFilePath := setupConfigJSON(t, root, configJSON)
			setupFile(t, root, "aliases.json", test.givenJSON)
			v := setupViper(configFilePath)
			vmAliases, err := getVMAliases(v)
			require.ErrorIs(err, test.expectedErr)
			require.Equal(test.expected, vmAliases)
		})
	}
}

func TestGetVMAliasesFromFlag(t *testing.T) {
	tests := map[string]struct {
		givenJSON   string
		expected    map[ids.ID][]string
		expectedErr error
	}{
		"wrong vm id": {
			givenJSON:   `{"wrongVmId": ["vm1","vm2"]}`,
			expected:    nil,
			expectedErr: errUnmarshalling,
		},
		"vm id": {
			givenJSON: `{"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": ["vm1","vm2"],
										"Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU": ["vm3", "vm4"] }`,
			expected: func() map[ids.ID][]string {
				m := map[ids.ID][]string{}
				id1, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
				id2, _ := ids.FromString("Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU")
				m[id1] = []string{"vm1", "vm2"}
				m[id2] = []string{"vm3", "vm4"}
				return m
			}(),
			expectedErr: nil,
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			require := require.New(t)
			encodedFileContent := base64.StdEncoding.EncodeToString([]byte(test.givenJSON))

			// build viper config
			v := setupViperFlags()
			v.Set(VMAliasesContentKey, encodedFileContent)

			vmAliases, err := getVMAliases(v)
			require.ErrorIs(err, test.expectedErr)
			require.Equal(test.expected, vmAliases)
		})
	}
}

func TestGetVMAliasesDefaultDir(t *testing.T) {
	require := require.New(t)
	root := t.TempDir()
	// changes internal package variable, since using defaultDir (under user home) is risky.
	defaultVMAliasFilePath = filepath.Join(root, "aliases.json")
	configFilePath := setupConfigJSON(t, root, "{}")

	v := setupViper(configFilePath)
	require.Equal(defaultVMAliasFilePath, v.GetString(VMAliasesFileKey))

	setupFile(t, root, "aliases.json", `{"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": ["vm1","vm2"]}`)
	vmAliases, err := getVMAliases(v)
	require.NoError(err)

	expected := map[ids.ID][]string{}
	id, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
	expected[id] = []string{"vm1", "vm2"}
	require.Equal(expected, vmAliases)
}

func TestGetVMAliasesDirNotExists(t *testing.T) {
	require := require.New(t)
	root := t.TempDir()
	aliasPath := "/not/exists"
	// set it explicitly
	configJSON := fmt.Sprintf(`{%q: %q}`, VMAliasesFileKey, aliasPath)
	configFilePath := setupConfigJSON(t, root, configJSON)
	v := setupViper(configFilePath)
	vmAliases, err := getVMAliases(v)
	require.ErrorIs(err, errFileDoesNotExist)
	require.Nil(vmAliases)

	// do not set it explicitly
	configJSON = "{}"
	configFilePath = setupConfigJSON(t, root, configJSON)
	v = setupViper(configFilePath)
	vmAliases, err = getVMAliases(v)
	require.Nil(vmAliases)
	require.NoError(err)
}

func TestGetNetConfigsFromFile(t *testing.T) {
	netID, err := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
	require.NoError(t, err)

	defaultConfigs := map[ids.ID]nets.Config{
		netID: getDefaultNetConfig(setupViperFlags()),
	}

	tests := map[string]struct {
		fileName    string
		givenJSON   string
		testF       func(*require.Assertions, map[ids.ID]nets.Config)
		expectedErr error
	}{
		"wrong config": {
			fileName:  "2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i.json",
			givenJSON: `thisisnotjson`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				require.Nil(given)
			},
			expectedErr: errUnmarshalling,
		},
		"chain is not tracked": {
			fileName:  "Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU.json",
			givenJSON: `{"validatorOnly": true}`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				require.Equal(defaultConfigs, given)
			},
			expectedErr: nil,
		},
		"default config when incorrect extension used": {
			fileName:  "2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i.yaml",
			givenJSON: `{"validatorOnly": true}`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				require.Equal(defaultConfigs, given)
			},
			expectedErr: nil,
		},
		"invalid consensus parameters": {
			fileName:  "2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i.json",
			givenJSON: `{"consensusParameters":{"k": 111, "alphaPreference":1234} }`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				require.Nil(given)
			},
			expectedErr: consensusconfig.ErrParametersInvalid,
		},
		"correct config": {
			fileName:  "2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i.json",
			givenJSON: `{"validatorOnly": true, "consensusParameters":{"k":20, "alphaPreference":15, "alphaConfidence":15} }`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				id, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
				config, ok := given[id]
				require.True(ok)

				require.True(config.ValidatorOnly)
				require.Equal(15, config.ConsensusParameters.AlphaConfidence)
				require.Equal(15, config.ConsensusParameters.AlphaPreference)
				require.Equal(20, config.ConsensusParameters.K)
			},
			expectedErr: nil,
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			require := require.New(t)

			root := t.TempDir()
			chainPath := filepath.Join(root, "chains")

			configJSON := fmt.Sprintf(`{%q: %q}`, NetConfigDirKey, chainPath)
			configFilePath := setupConfigJSON(t, root, configJSON)

			setupFile(t, chainPath, test.fileName, test.givenJSON)

			v := setupViper(configFilePath)
			chainConfigs, err := getNetConfigs(v, []ids.ID{netID})
			require.ErrorIs(err, test.expectedErr)
			if test.expectedErr != nil {
				return
			}
			test.testF(require, chainConfigs)
		})
	}
}

func TestGetNetConfigsFromFlags(t *testing.T) {
	netID, err := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
	require.NoError(t, err)

	defaultConfigs := map[ids.ID]nets.Config{
		netID: getDefaultNetConfig(setupViperFlags()),
	}

	tests := map[string]struct {
		givenJSON   string
		testF       func(*require.Assertions, map[ids.ID]nets.Config)
		expectedErr error
	}{
		"default config used when no config provided": {
			givenJSON: `{}`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				require.Equal(defaultConfigs, given)
			},
			expectedErr: nil,
		},
		"entry with no config": {
			givenJSON: `{"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i":{"consensusParameters":{"k":20,"alphaPreference":15,"alphaConfidence":15}}}`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				require.Len(given, 1)
				id, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
				config, ok := given[id]
				require.True(ok)
				// should have our specified params
				require.Equal(20, config.ConsensusParameters.K)
				require.Equal(15, config.ConsensusParameters.AlphaPreference)
				require.Equal(15, config.ConsensusParameters.AlphaConfidence)
			},
			expectedErr: nil,
		},
		"default config used when chain is not tracked": {
			givenJSON: `{"Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU":{"validatorOnly":true}}`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				require.Equal(defaultConfigs, given)
			},
			expectedErr: nil,
		},
		"invalid consensus parameters": {
			givenJSON: `{
				"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": {
					"consensusParameters": {
						"k": 111,
						"alphaPreference": 1234
					}
				}
			}`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				require.Empty(given)
			},
			expectedErr: consensusconfig.ErrParametersInvalid,
		},
		"correct config": {
			givenJSON: `{
				"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": {
					"consensusParameters": {
						"k": 30,
						"alphaPreference": 16,
						"alphaConfidence": 20
					},
					"validatorOnly": true
				}
			}`,
			testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
				id, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
				config, ok := given[id]
				require.True(ok)
				require.True(config.ValidatorOnly)
				require.Equal(16, config.ConsensusParameters.AlphaPreference)
				require.Equal(20, config.ConsensusParameters.AlphaConfidence)
				require.Equal(30, config.ConsensusParameters.K)
				// must still respect defaults (MainnetParameters.MaxOutstandingItems = 1024)
				require.Equal(1024, config.ConsensusParameters.MaxOutstandingItems)
			},
			expectedErr: nil,
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			require := require.New(t)

			encodedFileContent := base64.StdEncoding.EncodeToString([]byte(test.givenJSON))

			// build viper config
			v := setupViperFlags()
			v.Set(NetConfigContentKey, encodedFileContent)

			chainConfigs, err := getNetConfigs(v, []ids.ID{netID})
			require.ErrorIs(err, test.expectedErr)
			if test.expectedErr != nil {
				return
			}
			test.testF(require, chainConfigs)
		})
	}
}

// setups config json file and writes content
func setupConfigJSON(t *testing.T, rootPath string, value string) string {
	configFilePath := filepath.Join(rootPath, "config.json")
	require.NoError(t, os.WriteFile(configFilePath, []byte(value), 0o600))
	return configFilePath
}

// setups file creates necessary path and writes value to it.
func setupFile(t *testing.T, path string, fileName string, value string) {
	require := require.New(t)

	require.NoError(os.MkdirAll(path, 0o700))
	filePath := filepath.Join(path, fileName)
	require.NoError(os.WriteFile(filePath, []byte(value), 0o600))
}

func setupViperFlags() *viper.Viper {
	v := viper.New()
	fs := BuildFlagSet()
	pflag.Parse()
	if err := v.BindPFlags(fs); err != nil {
		log.Fatal(err)
	}
	return v
}

func setupViper(configFilePath string) *viper.Viper {
	v := setupViperFlags()
	v.SetConfigFile(configFilePath)
	if err := v.ReadInConfig(); err != nil {
		log.Fatal(err)
	}
	return v
}

func TestSkipBootstrapConfig(t *testing.T) {
	v := viper.New()

	// Test default values
	v.SetDefault(SkipBootstrapKey, false)
	v.SetDefault(EnableAutominingKey, false)

	config, err := getBootstrapConfig(v, 1)
	require.NoError(t, err)
	require.False(t, config.SkipBootstrap)
	require.False(t, config.EnableAutomining)

	// Test with skip bootstrap enabled
	v.Set(SkipBootstrapKey, true)
	config, err = getBootstrapConfig(v, 1)
	require.NoError(t, err)
	require.True(t, config.SkipBootstrap)
	require.False(t, config.EnableAutomining)

	// Test with automining enabled
	v.Set(EnableAutominingKey, true)
	config, err = getBootstrapConfig(v, 1)
	require.NoError(t, err)
	require.True(t, config.SkipBootstrap)
	require.True(t, config.EnableAutomining)

	// Test with both disabled
	v.Set(SkipBootstrapKey, false)
	v.Set(EnableAutominingKey, false)
	config, err = getBootstrapConfig(v, 1)
	require.NoError(t, err)
	require.False(t, config.SkipBootstrap)
	require.False(t, config.EnableAutomining)
}

func TestDevModeFlags(t *testing.T) {
	tests := []struct {
		name             string
		skipBootstrap    bool
		enableAutomining bool
		expected         bool
	}{
		{
			name:             "both enabled",
			skipBootstrap:    true,
			enableAutomining: true,
			expected:         true,
		},
		{
			name:             "only skip bootstrap",
			skipBootstrap:    true,
			enableAutomining: false,
			expected:         true,
		},
		{
			name:             "only automining",
			skipBootstrap:    false,
			enableAutomining: true,
			expected:         true,
		},
		{
			name:             "both disabled",
			skipBootstrap:    false,
			enableAutomining: false,
			expected:         false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			v := viper.New()
			v.Set(SkipBootstrapKey, tt.skipBootstrap)
			v.Set(EnableAutominingKey, tt.enableAutomining)

			config, err := getBootstrapConfig(v, 1)
			require.NoError(t, err)
			require.Equal(t, tt.skipBootstrap, config.SkipBootstrap)
			require.Equal(t, tt.enableAutomining, config.EnableAutomining)

			// Check that at least one dev mode flag is set
			devModeEnabled := config.SkipBootstrap || config.EnableAutomining
			require.Equal(t, tt.expected, devModeEnabled)
		})
	}
}

// TestResolveXAssetID_FromSovereignGenesis covers the canonical
// behaviour the sovereign-L1 fix relies on: when the loaded platform
// genesis bakes an X-Chain, resolveXAssetID returns the runtime asset
// ID encoded IN the genesis (matches what FromConfig produces, matches
// what the running X-Chain reports via platform.getStakingAssetID).
//
// Critically: NOT constants.UTXOAssetIDFor(networkID). That value is
// network-id-keyed and would silently collide between two sovereign L1s
// sharing a primary-network ID.
func TestResolveXAssetID_FromSovereignGenesis(t *testing.T) {
	require := require.New(t)

	cfg := builder.GetConfig(constants.LocalID)
	require.NotNil(cfg)
	require.NotEmpty(cfg.XChainGenesis, "fixture must bake X-Chain")

	genesisBytes, expectedID, err := builder.FromConfig(cfg)
	require.NoError(err)
	require.NotEqual(ids.Empty, expectedID)

	gotID, err := resolveXAssetID(constants.LocalID, genesisBytes)
	require.NoError(err)
	require.Equal(expectedID, gotID,
		"resolveXAssetID must agree with FromConfig on the genesis-derived asset ID")
}

// TestResolveXAssetID_POnlyFallback verifies that when the platform
// genesis bakes no X-Chain (P-only mode), resolveXAssetID falls back
// to constants.UTXOAssetIDFor(networkID). That value is unused at
// runtime (no X-Chain to mint on) but keeps the existing nodeConfig
// shape consistent for downstream consumers.
func TestResolveXAssetID_POnlyFallback(t *testing.T) {
	require := require.New(t)

	pOnly := &pchaingenesis.Genesis{Chains: nil}
	pOnlyBytes, err := pchaingenesis.Codec.Marshal(pchaintxs.CodecVersion, pOnly)
	require.NoError(err)

	gotID, err := resolveXAssetID(42, pOnlyBytes)
	require.NoError(err)
	require.Equal(constants.UTXOAssetIDFor(42), gotID,
		"P-only must fall through to UTXOAssetIDFor(networkID)")
}

// TestResolveXAssetID_Malformed asserts that bad genesis bytes surface
// an error rather than silently returning ids.Empty (which would
// reintroduce the UTXOAssetIDFor fallback and defeat the fix on
// sovereign L1s where the fallback value is wrong).
func TestResolveXAssetID_Malformed(t *testing.T) {
	require := require.New(t)

	_, err := resolveXAssetID(1, []byte{0xff, 0xfe, 0xfd, 0xfc})
	require.Error(err)
}
