package config

import (
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strconv"

	"github.com/alyu/configparser"
)

// Config holds cached configuration values loaded once at startup.
type Config struct {
	path    string
	section *configparser.Section
	loaded  bool
}

var (
	defaultConfig *Config
	loadErr       error
)

// Init loads (or creates) the configuration once. Call early from main.
// It is safe to call multiple times (subsequent calls are no-ops if successful).
func Init() error {
	if defaultConfig != nil && defaultConfig.loaded {
		return loadErr
	}
	c := &Config{}
	c.path = configStat("radiostations.ini")
	if err := c.load(); err != nil {
		loadErr = err
		return err
	}
	defaultConfig = c
	defaultConfig.loaded = true
	return nil
}

func configStat(configFile string) string {
	xdgConfigPath := os.Getenv("XDG_CONFIG_HOME")
	if xdgConfigPath == "" {
		if runtime.GOOS == "windows" {
			xdgConfigPath = os.Getenv("USERPROFILE")
			if xdgConfigPath == "" {
				xdgConfigPath = "."
			}
			xdgConfigPath = filepath.Join(xdgConfigPath, ".config")
		} else {
			xdgConfigPath = os.Getenv("HOME")
			if xdgConfigPath == "" {
				xdgConfigPath = "."
			}
			xdgConfigPath = filepath.Join(xdgConfigPath, ".config")
		}
	}

	configFile = filepath.Join(xdgConfigPath, "gostations", configFile)

	if _, err := os.Stat(configFile); errors.Is(err, os.ErrNotExist) {
		log.Printf("Your stations config file seems to be missing. A default will be generated at %s.", configFile)
		if errs := createIniFile(configFile); len(errs) > 0 {
			for _, e := range errs {
				if e != nil {
					log.Printf("Error creating default config: %s", e.Error())
				}
			}
			log.Fatal("Cannot continue without a config file.")
		}
	}
	return configFile
}

func createIniFile(fpath string) []error {
	log.Printf("Creating config file: %s\n", fpath)
	var errorlist []error
	if err := os.MkdirAll(filepath.Dir(fpath), 0770); err != nil {
		errorlist = append(errorlist, err)
	}

	file, err := os.Create(fpath)
	if err != nil {
		errorlist = append(errorlist, err)
	} else {
		// only write if file was created
		writes := []string{
			"[DEFAULT]\n",
			"radio_browser.api=all.api.radio-browser.info\n",
			"player.command=mpv\n",
			"player.options=--no-video\n",
			"menu_items.max=9999\n",
		}
		for _, w := range writes {
			if _, err := file.Write([]byte(w)); err != nil {
				errorlist = append(errorlist, err)
			}
		}
		if cerr := file.Close(); cerr != nil {
			errorlist = append(errorlist, cerr)
		}
	}

	return errorlist
}

func (c *Config) load() error {
	configparser.Delimiter = "=" // set once
	cfg, err := configparser.Read(c.path)
	if err != nil {
		return fmt.Errorf("read config %s: %w", c.path, err)
	}
	sec, err := cfg.Section("DEFAULT")
	if err != nil {
		return fmt.Errorf("find DEFAULT section in %s: %w", c.path, err)
	}
	c.section = sec
	return nil
}

// Get returns the raw string value for key, or error if missing/empty.
func Get(key string) (string, error) {
	if err := Init(); err != nil {
		return "", err
	}
	if defaultConfig.section == nil {
		return "", errors.New("config not loaded")
	}
	val := defaultConfig.section.Options()[key]
	if val == "" {
		return "", fmt.Errorf("no value for option %q in config", key)
	}
	return val, nil
}

// MustGet is like Get but fatals on error (for precheck paths only).
func MustGet(key string) string {
	v, err := Get(key)
	if err != nil {
		log.Fatalf("config error for %s: %v", key, err)
	}
	return v
}

func str2int(s string) int {
	i, err := strconv.Atoi(s)
	if err != nil {
		return 9999
	}
	return i
}

// API returns the radio browser API host (or default on error for compat).
func API() string {
	if v, err := Get("radio_browser.api"); err == nil && v != "" {
		return v
	}
	return "all.api.radio-browser.info"
}

// Player returns the player command.
func Player() string {
	if v, err := Get("player.command"); err == nil && v != "" {
		return v
	}
	return "mpv"
}

// Options returns extra player options string.
func Options() string {
	v, _ := Get("player.options")
	return v
}

// MaxItems returns the menu limit.
func MaxItems() int {
	if v, err := Get("menu_items.max"); err == nil && v != "" {
		return str2int(v)
	}
	return 9999
}

// Path returns the resolved config file path (for diagnostics).
func Path() string {
	if defaultConfig != nil {
		return defaultConfig.path
	}
	return configStat("radiostations.ini")
}
