package config

import (
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"strings"

	"github.com/spf13/viper"
	"gopkg.in/ini.v1"
)

var (
	V    *viper.Viper
	ROOT string
)

const mainIniPath = "bin/conf/env.ini"

func init() {
	root, err := findConfigRoot()
	if err != nil {
		slog.Warn("can't find config root", "error", err)
		ROOT, _ = os.Getwd()
	} else {
		ROOT = root
	}

	configPath := filepath.Join(ROOT, mainIniPath)

	V = viper.New()

	// 使用 ini 包解析配置文件
	cfg, err := ini.Load(configPath)
	if err != nil {
		slog.Error("load config file error", "error", err)
		// 创建一个空的配置
		cfg = ini.Empty()
	}

	// 将 ini 配置加载到 viper 中
	loadIniToViper(cfg, V)

	if err = loadIncludeFiles(); err != nil {
		panic("load include files error:" + err.Error())
	}

	go signalReload()
}

func ReloadConfigFile() {
	configPath := filepath.Join(ROOT, mainIniPath)

	// 使用 ini 包重新加载配置文件
	cfg, err := ini.Load(configPath)
	if err != nil {
		slog.Error("reload config file error", "error", err)
		return
	}

	// 清空现有配置并重新加载
	V = viper.New()
	loadIniToViper(cfg, V)

	if err := loadIncludeFiles(); err != nil {
		slog.Error("reload include files error", "error", err)
		return
	}
	slog.Info("reload config file successfully")
}

func SaveConfigFile() error {
	configPath := filepath.Join(ROOT, mainIniPath)
	if err := V.WriteConfigAs(configPath); err != nil {
		slog.Error("save config file error", "error", err)
		return err
	}

	slog.Info("save config file successfully")
	return nil
}

func loadIncludeFiles() error {
	includeFile := GetString("include_files.path", "")
	if includeFile != "" {
		includeFiles := strings.Split(includeFile, ",")
		for _, file := range includeFiles {
			file = strings.TrimSpace(file)
			if file != "" {
				// 使用绝对路径或相对路径
				if !filepath.IsAbs(file) {
					file = filepath.Join(ROOT, file)
				}
				// 使用 ini 包加载包含的配置文件
				cfg, err := ini.Load(file)
				if err != nil {
					return fmt.Errorf("failed to read config file %s: %w", file, err)
				}
				// 合并配置到主 viper 实例
				loadIniToViper(cfg, V)
			}
		}
	}

	return nil
}

// GetString 获取字符串配置值，支持 section.key 格式
func GetString(key string, defaultValue string) string {
	if V.IsSet(key) {
		return V.GetString(key)
	}
	return defaultValue
}

// GetInt 获取整数配置值，支持 section.key 格式
func GetInt(key string, defaultValue int) int {
	if V.IsSet(key) {
		return V.GetInt(key)
	}
	return defaultValue
}

// GetBool 获取布尔配置值，支持 section.key 格式
func GetBool(key string, defaultValue bool) bool {
	if V.IsSet(key) {
		return V.GetBool(key)
	}
	return defaultValue
}

// GetSection 获取整个 section 的配置，返回 map[string]string
func GetSection(section string) (map[string]string, error) {
	// 从 viper 中获取 section，格式为 section.key
	sectionMap := make(map[string]string)

	// 遍历所有配置键，查找属于该 section 的配置
	allKeys := V.AllKeys()
	found := false
	for _, key := range allKeys {
		if strings.HasPrefix(key, section+".") {
			found = true
			subKey := strings.TrimPrefix(key, section+".")
			sectionMap[subKey] = V.GetString(key)
		}
	}

	if !found {
		return nil, fmt.Errorf("section %s not found", section)
	}
	return sectionMap, nil
}

// GetPath 获取路径配置值，如果是相对路径则基于 ROOT 解析为绝对路径
func GetPath(key string, defaultValue string) string {
	pathValue := GetString(key, defaultValue)
	if pathValue == "" {
		return defaultValue
	}
	// 如果是绝对路径，直接返回
	if filepath.IsAbs(pathValue) {
		return pathValue
	}
	// 相对路径基于 ROOT 解析
	return filepath.Join(ROOT, pathValue)
}

// loadIniToViper 将 ini 配置加载到 viper 中
func loadIniToViper(cfg *ini.File, v *viper.Viper) {
	for _, section := range cfg.Sections() {
		sectionName := section.Name()
		// 跳过默认 section (DEFAULT)
		if sectionName == ini.DEFAULT_SECTION {
			sectionName = ""
		}

		for _, key := range section.Keys() {
			keyName := key.Name()
			value := key.Value()

			// 构建 viper 键名：section.key 或 key (如果 section 为空)
			var viperKey string
			if sectionName != "" {
				viperKey = sectionName + "." + keyName
			} else {
				viperKey = keyName
			}

			v.Set(viperKey, value)
		}
	}
}

// fileExist 检查文件或目录是否存在
// 如果由 filename 指定的文件或目录存在则返回 true，否则返回 false
func fileExist(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil || os.IsExist(err)
}

func findConfigRoot() (string, error) {
	if execPath, err := os.Executable(); err == nil {
		if root := searchConfig(filepath.Dir(execPath)); root != "" {
			return root, nil
		}
	}

	if wd, err := os.Getwd(); err == nil {
		if root := searchConfig(wd); root != "" {
			return root, nil
		}
	}

	return "", fmt.Errorf("unable to locate %s", mainIniPath)
}

func searchConfig(start string) string {
	dir := start
	for {
		configPath := filepath.Join(dir, mainIniPath)
		if fileExist(configPath) {
			return dir
		}

		parent := filepath.Dir(dir)
		if parent == dir {
			break
		}
		dir = parent
	}
	return ""
}
