// 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 (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/pelletier/go-toml"
	"github.com/urfave/cli/v2"
	"gopkg.in/yaml.v2"
)

func SetFlagsFromConfigFile(ctx *cli.Context, filePath string) error {
	fileExtension := filepath.Ext(filePath)

	fileConfig := make(map[string]any)

	if fileExtension == ".yml" || fileExtension == ".yaml" {
		yamlFile, err := os.ReadFile(filePath)
		if err != nil {
			return err
		}
		err = yaml.Unmarshal(yamlFile, fileConfig)
		if err != nil {
			return err
		}
	} else if fileExtension == ".toml" {
		tomlFile, err := os.ReadFile(filePath)
		if err != nil {
			return err
		}
		err = toml.Unmarshal(tomlFile, &fileConfig)
		if err != nil {
			return err
		}
	} else {
		return errors.New("config files only accepted are .yaml and .toml")
	}

	// Flatten nested maps into dot-separated keys so that TOML nested tables
	// (e.g. [sentinel] / staticpeers = [...]) and TOML dotted keys
	// (e.g. sentinel.staticpeers = [...]) map to the correct CLI flag names.
	flat := flattenConfig(fileConfig, "")

	// sets global flags to value in yaml/toml file
	for key, value := range flat {
		if !ctx.IsSet(key) {
			if reflect.ValueOf(value).Kind() == reflect.Slice {
				sliceInterface := value.([]any)
				s := make([]string, len(sliceInterface))
				for i, v := range sliceInterface {
					s[i] = fmt.Sprintf("%v", v)
				}
				err := ctx.Set(key, strings.Join(s, ","))
				if err != nil {
					return fmt.Errorf("failed setting %s flag with values=%s error=%s", key, s, err)
				}
			} else {
				err := ctx.Set(key, fmt.Sprintf("%v", value))
				if err != nil {
					return fmt.Errorf("failed setting %s flag with value=%v error=%s", key, value, err)

				}
			}
		}
	}

	return nil
}

// flattenConfig recursively flattens a nested map[string]any into a flat map
// using dot-separated keys. This lets TOML nested tables and dotted keys both
// map naturally to CLI flag names (e.g. sentinel.staticpeers).
func flattenConfig(m map[string]any, prefix string) map[string]any {
	result := make(map[string]any)
	for k, v := range m {
		key := k
		if prefix != "" {
			key = prefix + "." + k
		}
		if nested, ok := v.(map[string]any); ok {
			for fk, fv := range flattenConfig(nested, key) {
				result[fk] = fv
			}
		} else if nested, ok := v.(map[any]any); ok {
			// gopkg.in/yaml.v2 unmarshals maps as map[interface{}]interface{}
			converted := convertYAMLMap(nested)
			for fk, fv := range flattenConfig(converted, key) {
				result[fk] = fv
			}
		} else {
			result[key] = v
		}
	}
	return result
}

// convertYAMLMap converts a map[any]any (produced by gopkg.in/yaml.v2) to
// map[string]any so it can be processed uniformly.
func convertYAMLMap(m map[any]any) map[string]any {
	result := make(map[string]any, len(m))
	for k, v := range m {
		result[fmt.Sprintf("%v", k)] = v
	}
	return result
}
