package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"qvm/internal/config"
	"qvm/internal/logging"
	"qvm/internal/vm"

	"github.com/spf13/cobra"
)

// DefaultFlakePath is the path to the bundled default flake (set at build time or relative to binary)
var DefaultFlakePath string

func init() {
	// Try to find the default flake relative to the executable
	if exe, err := os.Executable(); err == nil {
		// When installed via Nix, the flake is at ../share/qvm/default-vm relative to bin/qvm
		shareDir := filepath.Join(filepath.Dir(filepath.Dir(exe)), "share", "qvm", "default-vm")
		if _, err := os.Stat(filepath.Join(shareDir, "flake.nix")); err == nil {
			DefaultFlakePath = shareDir
		}
	}
	// Fallback for development: look in the project directory
	if DefaultFlakePath == "" {
		// Check common development paths
		candidates := []string{
			"flake/default-vm",
			"../flake/default-vm",
			filepath.Join(os.Getenv("HOME"), "projects/qvm/flake/default-vm"),
		}
		for _, candidate := range candidates {
			if _, err := os.Stat(filepath.Join(candidate, "flake.nix")); err == nil {
				DefaultFlakePath = candidate
				break
			}
		}
	}
}

func ensureUserFlake() error {
	// Check if user flake already exists
	if _, err := os.Stat(filepath.Join(config.UserFlake, "flake.nix")); err == nil {
		return nil // Already exists
	}

	// Need to copy default flake
	if DefaultFlakePath == "" {
		return fmt.Errorf("default flake not found - please copy a flake to %s", config.UserFlake)
	}

	logging.Info("Copying default flake to user config...")

	// Create the user flake directory
	if err := os.MkdirAll(config.UserFlake, 0755); err != nil {
		return fmt.Errorf("failed to create flake directory: %w", err)
	}

	// Copy flake.nix
	srcFlake := filepath.Join(DefaultFlakePath, "flake.nix")
	dstFlake := filepath.Join(config.UserFlake, "flake.nix")
	if err := copyFile(srcFlake, dstFlake); err != nil {
		return fmt.Errorf("failed to copy flake.nix: %w", err)
	}

	// Copy flake.lock if it exists
	srcLock := filepath.Join(DefaultFlakePath, "flake.lock")
	dstLock := filepath.Join(config.UserFlake, "flake.lock")
	if _, err := os.Stat(srcLock); err == nil {
		if err := copyFile(srcLock, dstLock); err != nil {
			return fmt.Errorf("failed to copy flake.lock: %w", err)
		}
	}

	logging.Info(fmt.Sprintf("Default flake copied to %s", config.UserFlake))
	return nil
}

func copyFile(src, dst string) error {
	data, err := os.ReadFile(src)
	if err != nil {
		return err
	}
	return os.WriteFile(dst, data, 0644)
}

var rebuildCmd = &cobra.Command{
	Use:   "rebuild",
	Short: "Rebuild the base VM image",
	Long: `Rebuild the base VM image from the NixOS flake.

Runs 'nix build' on ~/.config/qvm/flake and copies
the result to the base image location.

If the flake doesn't exist, the default flake will be
copied to ~/.config/qvm/flake first.

If the VM is running, you'll need to restart it to
use the new image.`,
	Run: func(cmd *cobra.Command, args []string) {
		logging.Info("Rebuilding base VM image...")

		if err := config.EnsureDirs(); err != nil {
			logging.Error(fmt.Sprintf("Failed to create directories: %v", err))
			os.Exit(1)
		}

		// Ensure user flake exists, copy default if not
		if err := ensureUserFlake(); err != nil {
			logging.Error(fmt.Sprintf("Failed to ensure user flake: %v", err))
			os.Exit(1)
		}

		logging.Info("Building disk image from flake...")

		diskImageLink := filepath.Join(config.StateDir, "disk-image-result")
		nixCmd := exec.Command("nix", "build", config.UserFlake+"#default", "--out-link", diskImageLink, "-L")
		nixCmd.Stdout = os.Stdout
		nixCmd.Stderr = os.Stderr

		if err := nixCmd.Run(); err != nil {
			logging.Error(fmt.Sprintf("Failed to build disk image: %v", err))
			os.Exit(1)
		}

		logging.Info("Copying qcow2 image to base image location...")

		qcowSource := filepath.Join(diskImageLink, "nixos.qcow2")
		if _, err := os.Stat(qcowSource); os.IsNotExist(err) {
			logging.Error(fmt.Sprintf("Built image missing nixos.qcow2 at %s", qcowSource))
			os.Exit(1)
		}

		os.Remove(config.BaseImage)

		cpCmd := exec.Command("cp", qcowSource, config.BaseImage)
		if err := cpCmd.Run(); err != nil {
			logging.Error(fmt.Sprintf("Failed to copy base image: %v", err))
			os.Exit(1)
		}

		logging.Info("Base disk image built successfully")

		logging.Info("Building VM runner script from flake...")

		vmRunnerLink := filepath.Join(config.StateDir, "vm-runner-result")
		nixCmd2 := exec.Command("nix", "build", config.UserFlake+"#vm", "--out-link", vmRunnerLink, "-L")
		nixCmd2.Stdout = os.Stdout
		nixCmd2.Stderr = os.Stderr

		if err := nixCmd2.Run(); err != nil {
			logging.Error(fmt.Sprintf("Failed to build VM runner: %v", err))
			os.Exit(1)
		}

		os.Remove(config.VMRunner)
		if err := os.Symlink(vmRunnerLink, config.VMRunner); err != nil {
			logging.Error(fmt.Sprintf("Failed to create VM runner symlink: %v", err))
			os.Exit(1)
		}

		logging.Info("VM runner script built successfully")
		logging.Info("Rebuild complete!")

		if vm.IsRunning() {
			logging.Warn("VM is currently running")
			fmt.Println("")
			fmt.Println("The new base image will only take effect after restarting the VM:")
			fmt.Println("  qvm stop")
			fmt.Println("  qvm start")
			fmt.Println("")
		}
	},
}
