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

// Package config provides configuration management for the Private Archive client backend.
// It handles loading, validation, and defaults for all backend settings.
package config

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"time"
)

// DefaultServerAddress is the official Private Archive server .onion address.
// This can be overridden at build time with:
//
//	go build -ldflags "-X github.com/privatebackup/client-backend/internal/config.DefaultServerAddress=your-server.onion"
var DefaultServerAddress = "42ng6tejfimucrfolbhqfy4qebdkhn3awg37w6hmnnnbrforui52xoyd.onion"

// Config holds all configuration for the client backend.
type Config struct {
	// Server configuration
	Server ServerConfig `yaml:"server" mapstructure:"server"`

	// Tor configuration
	Tor TorConfig `yaml:"tor" mapstructure:"tor"`

	// API server configuration
	API APIConfig `yaml:"api" mapstructure:"api"`

	// Performance settings
	Performance PerformanceConfig `yaml:"performance" mapstructure:"performance"`

	// Backup defaults
	Backup BackupConfig `yaml:"backup" mapstructure:"backup"`

	// Paths configuration
	Paths PathsConfig `yaml:"paths" mapstructure:"paths"`

	// Updater configuration
	Updates UpdatesConfig `yaml:"updates" mapstructure:"updates"`

	// Schedules defines automatic backup schedules loaded at daemon startup.
	Schedules []ScheduleConfig `yaml:"schedules" mapstructure:"schedules"`
}

// ScheduleConfig defines a named cron-based backup schedule.
type ScheduleConfig struct {
	Name  string   `yaml:"name" mapstructure:"name"`
	Cron  string   `yaml:"cron" mapstructure:"cron"`
	Paths []string `yaml:"paths" mapstructure:"paths"`
}

// ServerConfig holds server connection settings.
type ServerConfig struct {
	// Address is the .onion address of the backup server
	Address string `yaml:"address" mapstructure:"address"`

	// Timeout for server requests
	Timeout time.Duration `yaml:"timeout" mapstructure:"timeout"`

	// HealthCheckInterval for periodic server health checks
	HealthCheckInterval time.Duration `yaml:"health_check_interval" mapstructure:"health_check_interval"`
}

// TorConfig holds embedded Tor settings.
type TorConfig struct {
	// DataDir for Tor cached state (speeds up reconnection)
	DataDir string `yaml:"data_dir" mapstructure:"data_dir"`

	// ConnectTimeout for Tor circuit establishment
	ConnectTimeout time.Duration `yaml:"connect_timeout" mapstructure:"connect_timeout"`

	// CircuitTimeout for idle circuit timeout
	CircuitTimeout time.Duration `yaml:"circuit_timeout" mapstructure:"circuit_timeout"`

	// MaxRetries is the maximum number of connection attempts (default: 3)
	MaxRetries int `yaml:"max_retries" mapstructure:"max_retries"`

	// RetryDelay is the delay between retry attempts (default: 5 seconds)
	RetryDelay time.Duration `yaml:"retry_delay" mapstructure:"retry_delay"`

	// ClearStateOnRetry clears Tor cached state before retrying (default: true)
	ClearStateOnRetry bool `yaml:"clear_state_on_retry" mapstructure:"clear_state_on_retry"`

	// ExternalSOCKS is the address of an external SOCKS5 proxy (e.g., "127.0.0.1:9050")
	// If set, uses the external proxy instead of starting an embedded Tor process.
	// This is useful when go-libtor is not available (non-Linux platforms) or
	// when you prefer to use a system Tor installation.
	ExternalSOCKS string `yaml:"external_socks" mapstructure:"external_socks"`
}

// APIConfig holds REST API server settings.
type APIConfig struct {
	// Listen address for the API server
	Listen string `yaml:"listen" mapstructure:"listen"`

	// AuthEnabled enables bearer token authentication
	AuthEnabled bool `yaml:"auth_enabled" mapstructure:"auth_enabled"`

	// AuthToken for API authentication (if enabled)
	AuthToken string `yaml:"auth_token" mapstructure:"auth_token"`

	// ReadTimeout for API requests
	ReadTimeout time.Duration `yaml:"read_timeout" mapstructure:"read_timeout"`

	// WriteTimeout for API responses
	WriteTimeout time.Duration `yaml:"write_timeout" mapstructure:"write_timeout"`
}

// PerformanceConfig holds resource constraint settings.
type PerformanceConfig struct {
	// GOMAXPROCS limits CPU cores (1 = minimal impact)
	GOMAXPROCS int `yaml:"gomaxprocs" mapstructure:"gomaxprocs"`

	// MaxConnections limits concurrent server connections
	MaxConnections int `yaml:"max_connections" mapstructure:"max_connections"`

	// Compression level for backups (off, auto, max)
	Compression string `yaml:"compression" mapstructure:"compression"`
}

// Mode determines which features are available in the client.
type Mode string

const (
	// ModeSimple provides a simplified interface with automatic retention.
	ModeSimple Mode = "simple"
	// ModeAdvanced provides full access to all restic features.
	ModeAdvanced Mode = "advanced"
)

// RetentionConfig holds automatic retention policy settings.
// Uses --keep-within-* flags for append-only mode safety.
type RetentionConfig struct {
	// KeepWithinDaily keeps all daily snapshots within this duration (e.g., "7d").
	KeepWithinDaily string `yaml:"keep_within_daily" mapstructure:"keep_within_daily"`

	// KeepWithinWeekly keeps all weekly snapshots within this duration (e.g., "28d").
	KeepWithinWeekly string `yaml:"keep_within_weekly" mapstructure:"keep_within_weekly"`

	// KeepWithinMonthly keeps all monthly snapshots within this duration (e.g., "12m").
	KeepWithinMonthly string `yaml:"keep_within_monthly" mapstructure:"keep_within_monthly"`

	// KeepWithinYearly keeps all yearly snapshots within this duration (e.g., "10y").
	KeepWithinYearly string `yaml:"keep_within_yearly" mapstructure:"keep_within_yearly"`
}

// DefaultRetentionConfig returns the default append-only safe retention policy.
func DefaultRetentionConfig() RetentionConfig {
	return RetentionConfig{
		KeepWithinDaily:   "7d",
		KeepWithinWeekly:  "28d", // 4 weeks (restic doesn't support "4w")
		KeepWithinMonthly: "12m",
		KeepWithinYearly:  "10y",
	}
}

// BackupConfig holds default backup settings.
type BackupConfig struct {
	// ExcludeCaches excludes directories with CACHEDIR.TAG
	ExcludeCaches bool `yaml:"exclude_caches" mapstructure:"exclude_caches"`

	// DefaultExcludes are patterns excluded by default
	DefaultExcludes []string `yaml:"default_excludes" mapstructure:"default_excludes"`

	// Paths contains the list of paths to back up by default
	Paths []string `yaml:"paths" mapstructure:"paths"`

	// MissingPathBehavior determines how to handle paths that don't exist:
	// "warn" (default), "skip", or "error"
	MissingPathBehavior string `yaml:"missing_path_behavior" mapstructure:"missing_path_behavior"`

	// Mode determines simple vs advanced feature availability
	Mode Mode `yaml:"mode" mapstructure:"mode"`

	// Retention holds the automatic retention policy (used in simple mode)
	Retention RetentionConfig `yaml:"retention" mapstructure:"retention"`

	// AutoPrune enables automatic pruning after each backup (simple mode)
	AutoPrune bool `yaml:"auto_prune" mapstructure:"auto_prune"`
}

// UpdatesConfig holds automatic update settings.
//
// Disabling AutoUpdate is allowed but discouraged: clients that fall too far
// behind the server's minimum version are eventually rejected by the
// server. The frontend surfaces this as a persistent banner warning.
type UpdatesConfig struct {
	// AutoUpdate enables background polling and automatic install of new
	// signed releases. Default: true.
	AutoUpdate bool `yaml:"auto_update" mapstructure:"auto_update"`

	// Channel selects which manifest to follow (e.g. "stable").
	Channel string `yaml:"channel" mapstructure:"channel"`

	// ManifestURL overrides the default public update endpoint. Leave
	// empty to use the built-in https://privatearchive.xyz/updates/...
	// host. Useful for staging or air-gapped mirrors.
	ManifestURL string `yaml:"manifest_url" mapstructure:"manifest_url"`

	// PollInterval is the average gap between background checks. Defaults
	// to 6h. Each check has up to ±25% jitter applied automatically.
	PollInterval time.Duration `yaml:"poll_interval" mapstructure:"poll_interval"`
}

// DefaultUpdatesConfig returns the default updater configuration: enabled,
// stable channel, official manifest host, 6h poll interval.
func DefaultUpdatesConfig() UpdatesConfig {
	return UpdatesConfig{
		AutoUpdate:   true,
		Channel:      "stable",
		ManifestURL:  "", // empty -> updater package's DefaultManifestURL
		PollInterval: 6 * time.Hour,
	}
}

// PathsConfig holds file path settings.
type PathsConfig struct {
	// ConfigDir is the directory for configuration files
	ConfigDir string `yaml:"config_dir" mapstructure:"config_dir"`

	// KeyFile is the path to the backup key file
	KeyFile string `yaml:"key_file" mapstructure:"key_file"`

	// CacheDir is the directory for cache files
	CacheDir string `yaml:"cache_dir" mapstructure:"cache_dir"`

	// LogFile is the path to the log file (empty for stdout)
	LogFile string `yaml:"log_file" mapstructure:"log_file"`

	// WebDir is the directory containing the built web UI (index.html + assets).
	// When empty (default), the daemon serves API only. When set, anything not
	// matched by /api/* is served from this directory, with SPA fallback to
	// index.html. Used by the all-in-one container image.
	WebDir string `yaml:"web_dir" mapstructure:"web_dir"`
}

// DefaultConfig returns a configuration with sensible defaults.
func DefaultConfig() *Config {
	homeDir, _ := os.UserHomeDir()
	configDir := filepath.Join(homeDir, ".config", "privatebackup")

	return &Config{
		Server: ServerConfig{
			Address:             DefaultServerAddress, // Official server, override via config/env
			Timeout:             5 * time.Minute,
			HealthCheckInterval: 5 * time.Minute,
		},
		Tor: TorConfig{
			DataDir:           filepath.Join(configDir, "tor"),
			ConnectTimeout:    2 * time.Minute,
			CircuitTimeout:    10 * time.Minute,
			MaxRetries:        3,
			RetryDelay:        5 * time.Second,
			ClearStateOnRetry: true,
		},
		API: APIConfig{
			Listen:       "127.0.0.1:62774",
			AuthEnabled:  false,
			AuthToken:    "",
			ReadTimeout:  30 * time.Second,
			WriteTimeout: 5 * time.Minute, // Long for uploads
		},
		Performance: PerformanceConfig{
			GOMAXPROCS:     1,
			MaxConnections: 1,
			Compression:    "max",
		},
		Backup: BackupConfig{
			ExcludeCaches: true,
			DefaultExcludes: []string{
				// OS files
				"**/.DS_Store",
				"**/Thumbs.db",
				"**/desktop.ini",
				"**/.Trash*",
				"$RECYCLE.BIN/**",
				// Cache directories
				"**/.cache/**",
				"**/Cache/**",
				"**/cache/**",
				"**/Caches/**",
				// Package manager caches
				"**/node_modules/**",
				"**/.npm/**",
				"**/.yarn/**",
				"**/vendor/**",
				"**/__pycache__/**",
				"**/.venv/**",
				"**/venv/**",
				// Build outputs
				"**/target/**",
				"**/build/**",
				"**/dist/**",
				"**/*.o",
				"**/*.pyc",
				// VCS internals
				"**/.git/objects/**",
				"**/.svn/**",
				// Temporary files
				"**/*.tmp",
				"**/*.temp",
				"**/*.swp",
				"**/*~",
			},
			Paths:               []string{},
			MissingPathBehavior: "warn",
			Mode:                ModeSimple,
			Retention:           DefaultRetentionConfig(),
			AutoPrune:           true,
		},
		Paths: PathsConfig{
			ConfigDir: configDir,
			KeyFile:   filepath.Join(configDir, "privatebackup-key.pem"),
			CacheDir:  filepath.Join(configDir, "cache"),
			LogFile:   "", // stdout by default
		},
		Updates: DefaultUpdatesConfig(),
	}
}

// Validate checks the configuration for errors.
func (c *Config) Validate() error {
	var errs []error

	// Server validation
	if c.Server.Address == "" {
		errs = append(errs, errors.New("server.address is required"))
	}
	if c.Server.Timeout <= 0 {
		errs = append(errs, errors.New("server.timeout must be positive"))
	}

	// Tor validation
	if c.Tor.DataDir == "" {
		errs = append(errs, errors.New("tor.data_dir is required"))
	}
	if c.Tor.ConnectTimeout <= 0 {
		errs = append(errs, errors.New("tor.connect_timeout must be positive"))
	}

	// API validation
	if c.API.Listen == "" {
		errs = append(errs, errors.New("api.listen is required"))
	}
	if c.API.AuthEnabled && c.API.AuthToken == "" {
		errs = append(errs, errors.New("api.auth_token is required when auth is enabled"))
	}

	// Performance validation
	if c.Performance.GOMAXPROCS < 1 {
		errs = append(errs, errors.New("performance.gomaxprocs must be at least 1"))
	}
	if c.Performance.MaxConnections < 1 {
		errs = append(errs, errors.New("performance.max_connections must be at least 1"))
	}
	validCompressions := map[string]bool{"off": true, "auto": true, "max": true}
	if !validCompressions[c.Performance.Compression] {
		errs = append(errs, fmt.Errorf("performance.compression must be one of: off, auto, max"))
	}

	// Paths validation
	if c.Paths.ConfigDir == "" {
		errs = append(errs, errors.New("paths.config_dir is required"))
	}
	if c.Paths.KeyFile == "" {
		errs = append(errs, errors.New("paths.key_file is required"))
	}

	if len(errs) > 0 {
		return fmt.Errorf("configuration errors: %v", errs)
	}
	return nil
}

// ApplyPerformanceSettings applies performance constraints from config.
func (c *Config) ApplyPerformanceSettings() {
	if c.Performance.GOMAXPROCS > 0 {
		runtime.GOMAXPROCS(c.Performance.GOMAXPROCS)
	}
}

// EnsureDirectories creates required directories if they don't exist.
func (c *Config) EnsureDirectories() error {
	dirs := []string{
		c.Paths.ConfigDir,
		c.Paths.CacheDir,
		c.Tor.DataDir,
	}

	for _, dir := range dirs {
		if dir == "" {
			continue
		}
		if err := os.MkdirAll(dir, 0700); err != nil {
			return fmt.Errorf("failed to create directory %s: %w", dir, err)
		}
	}

	return nil
}
