// Copyright 2024 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package cli

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/urfave/cli/v3"
)

func TestSetFlagsFromConfigFile_YAML(t *testing.T) {
	staticPeersFlag := cli.StringFlag{Name: "staticpeers", Value: ""}
	sentinelStaticPeersFlag := cli.StringSliceFlag{Name: "sentinel.staticpeers"}
	datadirFlag := cli.StringFlag{Name: "datadir", Value: ""}

	tests := []struct {
		name                    string
		ext                     string // ".yaml" or ".yml"
		yaml                    string
		wantStaticPeers         string
		wantSentinelStaticPeers []string
		wantDatadir             string
	}{
		{
			name:        "flat scalar value, .yaml extension",
			ext:         ".yaml",
			yaml:        "datadir: /tmp/erigon\n",
			wantDatadir: "/tmp/erigon",
		},
		{
			name:        "flat scalar value, .yml extension",
			ext:         ".yml",
			yaml:        "datadir: /tmp/erigon\n",
			wantDatadir: "/tmp/erigon",
		},
		{
			name:            "YAML sequence for staticpeers",
			ext:             ".yaml",
			yaml:            "staticpeers:\n  - enode://abc@1.2.3.4:30303\n  - enode://def@5.6.7.8:30303\n",
			wantStaticPeers: "enode://abc@1.2.3.4:30303,enode://def@5.6.7.8:30303",
		},
		{
			// Nested YAML map: yaml.v3 decodes these as map[string]any directly,
			// whereas yaml.v2 produced map[interface{}]interface{} and required
			// the now-deleted convertYAMLMap workaround.
			name:                    "nested YAML map for sentinel.staticpeers",
			ext:                     ".yaml",
			yaml:                    "sentinel:\n  staticpeers:\n    - enode://abc@1.2.3.4:9000\n    - enode://def@5.6.7.8:9000\n",
			wantSentinelStaticPeers: []string{"enode://abc@1.2.3.4:9000", "enode://def@5.6.7.8:9000"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cfgFile := filepath.Join(t.TempDir(), "erigon"+tt.ext)
			require.NoError(t, os.WriteFile(cfgFile, []byte(tt.yaml), 0o600))

			// urfave/cli v3 flags carry parse state, so use fresh copies per run.
			staticPeers := staticPeersFlag
			sentinelStaticPeers := sentinelStaticPeersFlag
			datadir := datadirFlag
			app := &cli.Command{}
			app.Flags = []cli.Flag{&staticPeers, &sentinelStaticPeers, &datadir}
			app.Action = func(_ context.Context, ctx *cli.Command) error {
				err := SetFlagsFromConfigFile(ctx, cfgFile)
				require.NoError(t, err)

				if tt.wantDatadir != "" {
					require.Equal(t, tt.wantDatadir, ctx.String("datadir"))
				}
				if tt.wantStaticPeers != "" {
					require.True(t, ctx.IsSet("staticpeers"))
					require.Equal(t, tt.wantStaticPeers, ctx.String("staticpeers"))
				}
				if tt.wantSentinelStaticPeers != nil {
					require.True(t, ctx.IsSet("sentinel.staticpeers"))
					require.Equal(t, tt.wantSentinelStaticPeers, ctx.StringSlice("sentinel.staticpeers"))
				}
				return nil
			}
			require.NoError(t, app.Run(context.Background(), []string{"erigon"}))
		})
	}
}

func TestSetFlagsFromConfigFile_StaticPeers(t *testing.T) {
	staticPeersFlag := cli.StringFlag{
		Name:  "staticpeers",
		Usage: "Comma separated enode URLs to connect to",
		Value: "",
	}
	sentinelStaticPeersFlag := cli.StringSliceFlag{
		Name:  "sentinel.staticpeers",
		Usage: "connect to comma-separated Consensus static peers",
	}

	tests := []struct {
		name                    string
		toml                    string
		wantStaticPeers         string
		wantSentinelStaticPeers []string
	}{
		{
			name:            "EL staticpeers as TOML array",
			toml:            "staticpeers = [\"enode://abc@1.2.3.4:30303\", \"enode://def@5.6.7.8:30303\"]\n",
			wantStaticPeers: "enode://abc@1.2.3.4:30303,enode://def@5.6.7.8:30303",
		},
		{
			name:            "EL staticpeers as TOML string",
			toml:            "staticpeers = \"enode://abc@1.2.3.4:30303,enode://def@5.6.7.8:30303\"\n",
			wantStaticPeers: "enode://abc@1.2.3.4:30303,enode://def@5.6.7.8:30303",
		},
		{
			name:                    "CL sentinel.staticpeers as TOML dotted key",
			toml:                    "sentinel.staticpeers = [\"enode://abc@1.2.3.4:9000\", \"enode://def@5.6.7.8:9000\"]\n",
			wantSentinelStaticPeers: []string{"enode://abc@1.2.3.4:9000", "enode://def@5.6.7.8:9000"},
		},
		{
			name:                    "CL sentinel.staticpeers as TOML nested table",
			toml:                    "[sentinel]\nstaticpeers = [\"enode://abc@1.2.3.4:9000\", \"enode://def@5.6.7.8:9000\"]\n",
			wantSentinelStaticPeers: []string{"enode://abc@1.2.3.4:9000", "enode://def@5.6.7.8:9000"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			cfgFile := filepath.Join(tmpDir, "erigon.toml")
			require.NoError(t, os.WriteFile(cfgFile, []byte(tt.toml), 0o600))

			// fresh copies per run (v3 flags carry parse state).
			staticPeers := staticPeersFlag
			sentinelStaticPeers := sentinelStaticPeersFlag
			app := &cli.Command{}
			app.Flags = []cli.Flag{&staticPeers, &sentinelStaticPeers}
			app.Action = func(_ context.Context, ctx *cli.Command) error {
				err := SetFlagsFromConfigFile(ctx, cfgFile)
				require.NoError(t, err)

				if tt.wantStaticPeers != "" {
					require.True(t, ctx.IsSet("staticpeers"), "ctx.IsSet(staticpeers) should be true")
					require.Equal(t, tt.wantStaticPeers, ctx.String("staticpeers"))
				}
				if tt.wantSentinelStaticPeers != nil {
					require.True(t, ctx.IsSet("sentinel.staticpeers"), "ctx.IsSet(sentinel.staticpeers) should be true")
					require.Equal(t, tt.wantSentinelStaticPeers, ctx.StringSlice("sentinel.staticpeers"))
				}
				return nil
			}

			err := app.Run(context.Background(), []string{"erigon"})
			require.NoError(t, err)
		})
	}
}
