package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"metis/pkg/inventory"
	"metis/pkg/plan"
)

var (
	fatalf = log.Fatalf
	exit   = os.Exit
)

func main() {
	if len(os.Args) < 2 {
		usage()
		exit(1)
	}
	switch os.Args[1] {
	case "plan":
		planCmd(os.Args[2:])
	case "burn":
		burnCmd(os.Args[2:])
	case "image":
		imageCmd(os.Args[2:])
	case "serve":
		serveCmd(os.Args[2:])
	case "inject":
		injectCmd(os.Args[2:])
	case "config":
		configCmd(os.Args[2:])
	case "facts":
		factsCmd(os.Args[2:])
	case "remote-devices":
		remoteDevicesCmd(os.Args[2:])
	case "remote-build":
		remoteBuildCmd(os.Args[2:])
	case "remote-flash":
		remoteFlashCmd(os.Args[2:])
	default:
		usage()
		exit(1)
	}
}

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: metis <plan|burn|image|serve|inject|config|facts|remote-devices|remote-build|remote-flash> [options]\n")
}

func loadInventory(path string) *inventory.Inventory {
	inv, err := inventory.Load(path)
	if err != nil {
		fatalf("load inventory: %v", err)
	}
	return inv
}

func planCmd(args []string) {
	fs := flag.NewFlagSet("plan", flag.ExitOnError)
	invPath := fs.String("inventory", "inventory.yaml", "inventory file")
	node := fs.String("node", "", "target node")
	device := fs.String("device", "/dev/sdX", "target block device")
	cache := fs.String("cache", filepath.Join(os.TempDir(), "metis-cache"), "image cache dir")
	boot := fs.String("boot", "", "mounted boot path for injection (optional)")
	root := fs.String("root", "", "mounted root path for injection (optional)")
	fs.Parse(args)
	if *node == "" {
		fatalf("--node is required")
	}
	inv := loadInventory(*invPath)
	if *boot != "" {
		os.Setenv("METIS_BOOT_PATH", *boot)
	}
	if *root != "" {
		os.Setenv("METIS_ROOT_PATH", *root)
	}
	p, err := plan.Build(inv, *node, *device, *cache)
	if err != nil {
		fatalf("build plan: %v", err)
	}
	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	_ = enc.Encode(p)
}

func burnCmd(args []string) {
	fs := flag.NewFlagSet("burn", flag.ExitOnError)
	invPath := fs.String("inventory", "inventory.yaml", "inventory file")
	node := fs.String("node", "", "target node")
	device := fs.String("device", "", "target block device (e.g. /dev/sdX)")
	cache := fs.String("cache", filepath.Join(os.TempDir(), "metis-cache"), "image cache dir")
	boot := fs.String("boot", "", "mounted boot path for injection (optional)")
	root := fs.String("root", "", "mounted root path for injection (optional)")
	autoMount := fs.Bool("auto-mount", false, "auto-mount boot/root for injection (linux, requires privileges)")
	confirm := fs.Bool("yes", false, "actually write to device")
	fs.Parse(args)
	if *node == "" || *device == "" {
		fatalf("--node and --device are required")
	}
	inv := loadInventory(*invPath)
	if *boot != "" {
		os.Setenv("METIS_BOOT_PATH", *boot)
	}
	if *root != "" {
		os.Setenv("METIS_ROOT_PATH", *root)
	}
	if *autoMount {
		os.Setenv("METIS_AUTO_MOUNT", "1")
	}
	p, err := plan.Execute(inv, *node, *device, *cache, *confirm)
	if err != nil {
		fatalf("burn: %v", err)
	}
	fmt.Printf("Plan for %s to %s:\n", p.Node, p.Device)
	for _, a := range p.Actions {
		fmt.Printf("- [%s] %s\n", a.Type, a.Detail)
	}
	if !*confirm {
		fmt.Printf("\nDry run. Re-run with --yes to execute.\n")
		return
	}
	fmt.Println("\nBurn complete. Safely eject and insert the SD into the node.")
}
