// SPDX-License-Identifier: GPL-3.0-or-later

// Package config provides configuration management for the CLI.
package config

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/spf13/viper"
)

// Config represents the CLI configuration.
type Config struct {
	// Output preferences
	Output OutputConfig `mapstructure:"output"`
	// Backend connection
	Backend BackendConfig `mapstructure:"backend"`
	// Default command options
	Defaults DefaultsConfig `mapstructure:"defaults"`
	// Shell integration
	Shell ShellConfig `mapstructure:"shell"`
}

// OutputConfig represents output preferences.
type OutputConfig struct {
	// Color mode: "auto", "always", "never"
	Color string `mapstructure:"color"`
	// Progress bar style: "bar", "percentage", "none"
	ProgressStyle string `mapstructure:"progress_style"`
	// Date format (Go time format)
	DateFormat string `mapstructure:"date_format"`
	// Size units: "auto", "binary", "decimal"
	SizeUnits string `mapstructure:"size_units"`
}

// BackendConfig represents backend connection settings.
type BackendConfig struct {
	// URL of the backend API
	URL string `mapstructure:"url"`
	// Connection timeout
	Timeout time.Duration `mapstructure:"timeout"`
	// Auto-start daemon if not running
	AutoStartDaemon bool `mapstructure:"auto_start_daemon"`
}

// DefaultsConfig represents default command options.
type DefaultsConfig struct {
	Backup BackupDefaults `mapstructure:"backup"`
	Forget ForgetDefaults `mapstructure:"forget"`
}

// BackupDefaults represents default backup options.
type BackupDefaults struct {
	ExcludeCaches bool `mapstructure:"exclude_caches"`
	OneFileSystem bool `mapstructure:"one_file_system"`
}

// ForgetDefaults represents default forget/retention options.
type ForgetDefaults struct {
	KeepLast    int `mapstructure:"keep_last"`
	KeepDaily   int `mapstructure:"keep_daily"`
	KeepWeekly  int `mapstructure:"keep_weekly"`
	KeepMonthly int `mapstructure:"keep_monthly"`
	KeepYearly  int `mapstructure:"keep_yearly"`
}

// ShellConfig represents shell integration settings.
type ShellConfig struct {
	Aliases map[string]string `mapstructure:"aliases"`
}

// Manager handles configuration loading and saving.
type Manager struct {
	v          *viper.Viper
	configDir  string
	configFile string
}

// NewManager creates a new configuration manager.
func NewManager() *Manager {
	return &Manager{
		v: viper.New(),
	}
}

// SetConfigDir sets the configuration directory.
func (m *Manager) SetConfigDir(dir string) {
	m.configDir = dir
}

// SetConfigFile sets a specific configuration file path.
func (m *Manager) SetConfigFile(path string) {
	m.configFile = path
}

// Load loads the configuration from file and environment.
func (m *Manager) Load() (*Config, error) {
	// Set defaults
	m.setDefaults()

	// Configure viper
	m.v.SetConfigType("yaml")

	if m.configFile != "" {
		// Use specific config file
		m.v.SetConfigFile(m.configFile)
	} else {
		// Use default locations
		configDir := m.getConfigDir()
		m.v.SetConfigName("cli")
		m.v.AddConfigPath(configDir)
	}

	// Environment variables
	m.v.SetEnvPrefix("PRIVATEBACKUP")
	m.v.AutomaticEnv()

	// Environment variable mappings
	_ = m.v.BindEnv("backend.url", "PRIVATEBACKUP_BACKEND_URL")

	// Read config file (ignore if not found)
	if err := m.v.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
			return nil, fmt.Errorf("failed to read config file: %w", err)
		}
	}

	// Unmarshal to struct
	cfg := &Config{}
	if err := m.v.Unmarshal(cfg); err != nil {
		return nil, fmt.Errorf("failed to parse config: %w", err)
	}

	return cfg, nil
}

// Save saves the configuration to file.
func (m *Manager) Save(cfg *Config) error {
	configDir := m.getConfigDir()

	// Ensure config directory exists
	if err := os.MkdirAll(configDir, 0700); err != nil {
		return fmt.Errorf("failed to create config directory: %w", err)
	}

	configPath := m.configFile
	if configPath == "" {
		configPath = filepath.Join(configDir, "cli.yaml")
	}

	// Update viper with config values using explicit key paths
	// (viper's Set with structs doesn't respect mapstructure tags)

	// Output settings
	m.v.Set("output.color", cfg.Output.Color)
	m.v.Set("output.progress_style", cfg.Output.ProgressStyle)
	m.v.Set("output.date_format", cfg.Output.DateFormat)
	m.v.Set("output.size_units", cfg.Output.SizeUnits)

	// Backend settings
	m.v.Set("backend.url", cfg.Backend.URL)
	m.v.Set("backend.timeout", cfg.Backend.Timeout)
	m.v.Set("backend.auto_start_daemon", cfg.Backend.AutoStartDaemon)

	// Backup defaults
	m.v.Set("defaults.backup.exclude_caches", cfg.Defaults.Backup.ExcludeCaches)
	m.v.Set("defaults.backup.one_file_system", cfg.Defaults.Backup.OneFileSystem)

	// Forget defaults
	m.v.Set("defaults.forget.keep_last", cfg.Defaults.Forget.KeepLast)
	m.v.Set("defaults.forget.keep_daily", cfg.Defaults.Forget.KeepDaily)
	m.v.Set("defaults.forget.keep_weekly", cfg.Defaults.Forget.KeepWeekly)
	m.v.Set("defaults.forget.keep_monthly", cfg.Defaults.Forget.KeepMonthly)
	m.v.Set("defaults.forget.keep_yearly", cfg.Defaults.Forget.KeepYearly)

	// Shell settings
	m.v.Set("shell.aliases", cfg.Shell.Aliases)

	// Write to file
	if err := m.v.WriteConfigAs(configPath); err != nil {
		return fmt.Errorf("failed to write config file: %w", err)
	}

	return nil
}

// GetConfigDir returns the configuration directory path.
func (m *Manager) GetConfigDir() string {
	return m.getConfigDir()
}

// getConfigDir returns the configuration directory.
func (m *Manager) getConfigDir() string {
	if m.configDir != "" {
		return m.configDir
	}

	// Check XDG_CONFIG_HOME
	if xdgConfig := os.Getenv("XDG_CONFIG_HOME"); xdgConfig != "" {
		return filepath.Join(xdgConfig, "privatebackup")
	}

	// Default to ~/.config/privatebackup
	home, err := os.UserHomeDir()
	if err != nil {
		return ".config/privatebackup"
	}

	return filepath.Join(home, ".config", "privatebackup")
}

// setDefaults sets the default configuration values.
func (m *Manager) setDefaults() {
	// Output defaults
	m.v.SetDefault("output.color", DefaultOutputColor)
	m.v.SetDefault("output.progress_style", DefaultProgressStyle)
	m.v.SetDefault("output.date_format", DefaultDateFormat)
	m.v.SetDefault("output.size_units", DefaultSizeUnits)

	// Backend defaults
	m.v.SetDefault("backend.url", DefaultBackendURL)
	m.v.SetDefault("backend.timeout", DefaultTimeout)
	m.v.SetDefault("backend.auto_start_daemon", DefaultAutoStartDaemon)

	// Backup defaults
	m.v.SetDefault("defaults.backup.exclude_caches", DefaultExcludeCaches)
	m.v.SetDefault("defaults.backup.one_file_system", DefaultOneFileSystem)

	// Forget defaults
	m.v.SetDefault("defaults.forget.keep_last", DefaultKeepLast)
	m.v.SetDefault("defaults.forget.keep_daily", DefaultKeepDaily)
	m.v.SetDefault("defaults.forget.keep_weekly", DefaultKeepWeekly)
	m.v.SetDefault("defaults.forget.keep_monthly", DefaultKeepMonthly)
	m.v.SetDefault("defaults.forget.keep_yearly", DefaultKeepYearly)
}

// Get returns the current viper value for a key.
func (m *Manager) Get(key string) interface{} {
	return m.v.Get(key)
}

// Set sets a configuration value.
func (m *Manager) Set(key string, value interface{}) {
	m.v.Set(key, value)
}

// GetString returns a string configuration value.
func (m *Manager) GetString(key string) string {
	return m.v.GetString(key)
}

// GetBool returns a boolean configuration value.
func (m *Manager) GetBool(key string) bool {
	return m.v.GetBool(key)
}

// GetInt returns an integer configuration value.
func (m *Manager) GetInt(key string) int {
	return m.v.GetInt(key)
}

// GetDuration returns a duration configuration value.
func (m *Manager) GetDuration(key string) time.Duration {
	return m.v.GetDuration(key)
}
