package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"time"

	"gopkg.in/yaml.v3"

	"git.warky.dev/wdevs/amcs/internal/config"
)

func main() {
	var (
		configPath string
		dryRun     bool
		toVersion  int
	)
	flag.StringVar(&configPath, "config", "", "Path to the YAML config file (default: $AMCS_CONFIG or ./configs/dev.yaml)")
	flag.BoolVar(&dryRun, "dry-run", false, "Print the migrated config to stdout instead of writing it back")
	flag.IntVar(&toVersion, "to-version", config.CurrentConfigVersion, "Stop migrating after reaching this version")
	flag.Parse()

	if toVersion <= 0 || toVersion > config.CurrentConfigVersion {
		log.Fatalf("invalid -to-version %d (must be between 1 and %d)", toVersion, config.CurrentConfigVersion)
	}

	path := config.ResolvePath(configPath)
	original, err := os.ReadFile(path)
	if err != nil {
		log.Fatalf("read config %q: %v", path, err)
	}

	raw := map[string]any{}
	if err := yaml.Unmarshal(original, &raw); err != nil {
		log.Fatalf("decode config %q: %v", path, err)
	}
	if raw == nil {
		raw = map[string]any{}
	}

	applied, err := migrateUpTo(raw, toVersion)
	if err != nil {
		log.Fatalf("migrate: %v", err)
	}

	if len(applied) == 0 {
		fmt.Fprintf(os.Stderr, "%s already at version %d; nothing to do\n", path, currentVersion(raw))
		return
	}

	out, err := yaml.Marshal(raw)
	if err != nil {
		log.Fatalf("marshal migrated config: %v", err)
	}

	for _, step := range applied {
		fmt.Fprintf(os.Stderr, "applied migration v%d -> v%d: %s\n", step.From, step.To, step.Describe)
	}

	if dryRun {
		_, _ = os.Stdout.Write(out)
		return
	}

	backup := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
	if err := os.WriteFile(backup, original, 0o600); err != nil {
		log.Fatalf("write backup %q: %v", backup, err)
	}
	if err := os.WriteFile(path, out, 0o600); err != nil {
		log.Fatalf("write migrated config %q: %v", path, err)
	}
	fmt.Fprintf(os.Stderr, "wrote migrated config to %s (backup: %s)\n", path, backup)
}

// migrateUpTo runs the migration ladder but stops at the requested version.
func migrateUpTo(raw map[string]any, target int) ([]config.ConfigMigration, error) {
	if currentVersion(raw) >= target {
		return nil, nil
	}
	if target == config.CurrentConfigVersion {
		return config.Migrate(raw)
	}
	// Partial migrations are rare; for now reject anything other than the
	// current version target since the migration ladder is short.
	return nil, fmt.Errorf("partial migration to v%d is not supported (use -to-version=%d)", target, config.CurrentConfigVersion)
}

func currentVersion(raw map[string]any) int {
	v, ok := raw["version"]
	if !ok {
		return 1
	}
	switch n := v.(type) {
	case int:
		return n
	case int64:
		return int(n)
	case float64:
		return int(n)
	}
	return 1
}
