package config

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

	"gopkg.in/yaml.v3"
)

type Config struct {
	Version   string `yaml:"-" json:"-"`
	DataDir   string `yaml:"data_dir" env:"SYNCSERVER_DATA_DIR" default:"./data"`
	ConfigDir string `yaml:"config_dir" env:"SYNCSERVER_CONFIG_DIR" default:"./etc/syncserver"`
	Addr      string `yaml:"addr" env:"SYNCSERVER_ADDR" default:":8080"`

	Auth AuthConfig `yaml:"auth"`

	Scheduler SchedulerConfig `yaml:"scheduler"`
}

type AuthConfig struct {
	JWTSecret   string `yaml:"jwt_secret" env:"SYNCSERVER_JWT_SECRET"`
	JWTExpiryH  int    `yaml:"jwt_expiry_hours" env:"SYNCSERVER_JWT_EXPIRY_HOURS" default:"24"`
	AdminUser   string `yaml:"-" env:"SYNCSERVER_ADMIN_USER"`
	AdminPass   string `yaml:"-" env:"SYNCSERVER_ADMIN_PASSWORD"`
}

type SchedulerConfig struct {
	Timezone       string `yaml:"timezone" env:"SYNCSERVER_SCHEDULER_TZ" default:"UTC"`
	RetentionDays  int    `yaml:"retention_days" env:"SYNCSERVER_RETENTION_DAYS" default:"30"`
}

var globalCfg *Config

func Load(configPath, dataDir, addr string) (*Config, error) {
	cfg := &Config{
		DataDir:   dataDir,
		ConfigDir: "./etc/syncserver",
		Addr:      addr,
		Auth: AuthConfig{
			JWTExpiryH: 24,
		},
		Scheduler: SchedulerConfig{
			Timezone:      "UTC",
			RetentionDays: 30,
		},
	}

	if configPath != "" {
		data, err := os.ReadFile(configPath)
		if err != nil && !os.IsNotExist(err) {
			return nil, fmt.Errorf("reading config: %w", err)
		}
		if err == nil {
			if err := yaml.Unmarshal(data, cfg); err != nil {
				return nil, fmt.Errorf("parsing config: %w", err)
			}
		}
	}

	prefix := "SYNCSERVER_"
	envs := []struct {
		key      *string
		envName  string
		required bool
	}{
		{&cfg.Auth.JWTSecret, prefix + "JWT_SECRET", false},
		{&cfg.Auth.AdminUser, prefix + "ADMIN_USER", false},
		{&cfg.Auth.AdminPass, prefix + "ADMIN_PASSWORD", false},
		{&cfg.DataDir, prefix + "DATA_DIR", false},
		{&cfg.Addr, prefix + "ADDR", false},
		{&cfg.Scheduler.Timezone, prefix + "SCHEDULER_TZ", false},
	}

	for _, e := range envs {
		if v := os.Getenv(e.envName); v != "" {
			*e.key = v
		}
	}

	if cfg.Auth.JWTSecret == "" {
		b := make([]byte, 32)
		f, err := os.Open("/dev/urandom")
		if err == nil {
			defer f.Close()
			n, _ := f.Read(b)
			if n == 32 {
				cfg.Auth.JWTSecret = fmt.Sprintf("%x", b)
			}
		}
		if cfg.Auth.JWTSecret == "" {
			cfg.Auth.JWTSecret = "insecure-dev-secret-change-in-production"
		}
	}

	if dataDir := os.Getenv("SYNCSERVER_DATA_DIR"); dataDir != "" {
		cfg.DataDir = dataDir
	}
	if addr := os.Getenv("SYNCSERVER_ADDR"); addr != "" {
		cfg.Addr = addr
	}

	absDataDir, err := filepath.Abs(cfg.DataDir)
	if err != nil {
		return nil, err
	}
	cfg.DataDir = absDataDir

	globalCfg = cfg
	return cfg, nil
}

func Get() *Config {
	return globalCfg
}

func (c *Config) DBPath() string {
	return filepath.Join(c.DataDir, "app.db")
}

func (c *Config) SSHDir() string {
	return filepath.Join(c.DataDir, "ssh")
}

func (c *Config) LogsDir() string {
	return filepath.Join(c.DataDir, "logs")
}

func (c *Config) EnsureDirs() error {
	dirs := []string{c.DataDir, c.SSHDir(), c.LogsDir()}
	for _, d := range dirs {
		if err := os.MkdirAll(d, 0700); err != nil {
			return fmt.Errorf("creating dir %s: %w", d, err)
		}
	}
	return nil
}

func (c *Config) LogPath() string {
	return filepath.Join(c.LogsDir(), "app.log")
}

func NormalizeAddr(addr string) string {
	addr = strings.TrimSpace(addr)
	if !strings.Contains(addr, ":") {
		addr = ":" + addr
	}
	return addr
}
