package cli

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

	"banger/internal/api"
	"banger/internal/rpc"

	"github.com/spf13/cobra"
)

func (d *deps) newKernelCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "kernel",
		Short: "Pull and manage Firecracker-compatible kernels",
		Long: strings.TrimSpace(`
Banger boots guests with a separate kernel artifact (vmlinux, plus
optional initrd + modules). Kernels are tracked by name in a local
catalog so multiple images can share one.

Most users never run these commands directly: 'banger image pull'
auto-pulls the kernel referenced by the catalog entry. Use these
commands when you want to inspect what's installed, switch a VM to
a different kernel via 'image register --kernel-ref', or import a
kernel built locally with scripts/make-*-kernel.sh.

Subcommands:
  pull       download a cataloged kernel by name
  list       show what's installed (or --available for the catalog)
  show       inspect one entry as JSON
  rm         remove a local kernel
  import     register a kernel built from scripts/make-*-kernel.sh
`),
		Example: strings.TrimSpace(`
  banger kernel list --available
  banger kernel pull generic-6.12
  banger kernel import void-kernel --from build/manual/void-kernel
`),
		RunE: helpNoArgs,
	}
	cmd.AddCommand(
		d.newKernelListCommand(),
		d.newKernelShowCommand(),
		d.newKernelRmCommand(),
		d.newKernelImportCommand(),
		d.newKernelPullCommand(),
	)
	return cmd
}

func (d *deps) newKernelPullCommand() *cobra.Command {
	var force bool
	cmd := &cobra.Command{
		Use:               "pull <name>",
		Short:             "Download a cataloged kernel bundle",
		Args:              exactArgsUsage(1, "usage: banger kernel pull <name> [--force]"),
		ValidArgsFunction: d.completeKernelCatalogNameOnlyAtPos0,
		RunE: func(cmd *cobra.Command, args []string) error {
			layout, _, err := d.ensureDaemon(cmd.Context())
			if err != nil {
				return err
			}
			var result api.KernelShowResult
			err = withHeartbeat(cmd.ErrOrStderr(), "kernel pull", func() error {
				var callErr error
				result, callErr = rpc.Call[api.KernelShowResult](cmd.Context(), layout.SocketPath, "kernel.pull", api.KernelPullParams{Name: args[0], Force: force})
				return callErr
			})
			if err != nil {
				return err
			}
			return printJSON(cmd.OutOrStdout(), result.Entry)
		},
	}
	cmd.Flags().BoolVar(&force, "force", false, "re-pull even if already present")
	return cmd
}

func (d *deps) newKernelImportCommand() *cobra.Command {
	var params api.KernelImportParams
	cmd := &cobra.Command{
		Use:   "import <name>",
		Short: "Import a kernel bundle produced by scripts/make-*-kernel.sh",
		Long:  "Copy the kernel, optional initrd, and optional modules directory from <from> into the local kernel catalog keyed by <name>. <from> is usually build/manual/void-kernel or build/manual/alpine-kernel.",
		Args:  exactArgsUsage(1, "usage: banger kernel import <name> --from <dir>"),
		RunE: func(cmd *cobra.Command, args []string) error {
			params.Name = args[0]
			if strings.TrimSpace(params.FromDir) == "" {
				return errors.New("--from <dir> is required")
			}
			abs, err := filepath.Abs(params.FromDir)
			if err != nil {
				return err
			}
			params.FromDir = abs
			layout, _, err := d.ensureDaemon(cmd.Context())
			if err != nil {
				return err
			}
			result, err := rpc.Call[api.KernelShowResult](cmd.Context(), layout.SocketPath, "kernel.import", params)
			if err != nil {
				return err
			}
			return printJSON(cmd.OutOrStdout(), result.Entry)
		},
	}
	cmd.Flags().StringVar(&params.FromDir, "from", "", "directory produced by make-*-kernel.sh (e.g. build/manual/void-kernel)")
	cmd.Flags().StringVar(&params.Distro, "distro", "", "distribution label stored in the manifest (e.g. void, alpine)")
	cmd.Flags().StringVar(&params.Arch, "arch", "", "architecture label stored in the manifest (e.g. x86_64)")
	return cmd
}

func (d *deps) newKernelListCommand() *cobra.Command {
	var available bool
	cmd := &cobra.Command{
		Use:     "list",
		Aliases: []string{"ls"},
		Short:   "List kernels (local by default, or --available for the catalog)",
		Args:    noArgsUsage("usage: banger kernel list [--available]"),
		RunE: func(cmd *cobra.Command, args []string) error {
			layout, _, err := d.ensureDaemon(cmd.Context())
			if err != nil {
				return err
			}
			if available {
				result, err := rpc.Call[api.KernelCatalogResult](cmd.Context(), layout.SocketPath, "kernel.catalog", api.Empty{})
				if err != nil {
					return err
				}
				return printKernelCatalogTable(cmd.OutOrStdout(), result.Entries)
			}
			result, err := rpc.Call[api.KernelListResult](cmd.Context(), layout.SocketPath, "kernel.list", api.Empty{})
			if err != nil {
				return err
			}
			return printKernelListTable(cmd.OutOrStdout(), result.Entries)
		},
	}
	cmd.Flags().BoolVar(&available, "available", false, "show the built-in catalog (with pulled/available status) instead of local entries")
	return cmd
}

func (d *deps) newKernelShowCommand() *cobra.Command {
	return &cobra.Command{
		Use:               "show <name>",
		Short:             "Show kernel catalog entry details",
		Args:              exactArgsUsage(1, "usage: banger kernel show <name>"),
		ValidArgsFunction: d.completeKernelNameOnlyAtPos0,
		RunE: func(cmd *cobra.Command, args []string) error {
			layout, _, err := d.ensureDaemon(cmd.Context())
			if err != nil {
				return err
			}
			result, err := rpc.Call[api.KernelShowResult](cmd.Context(), layout.SocketPath, "kernel.show", api.KernelRefParams{Name: args[0]})
			if err != nil {
				return err
			}
			return printJSON(cmd.OutOrStdout(), result.Entry)
		},
	}
}

func (d *deps) newKernelRmCommand() *cobra.Command {
	return &cobra.Command{
		Use:               "rm <name>",
		Aliases:           []string{"remove", "delete"},
		Short:             "Remove a kernel catalog entry",
		Args:              exactArgsUsage(1, "usage: banger kernel rm <name>"),
		ValidArgsFunction: d.completeKernelNameOnlyAtPos0,
		RunE: func(cmd *cobra.Command, args []string) error {
			layout, _, err := d.ensureDaemon(cmd.Context())
			if err != nil {
				return err
			}
			if _, err := rpc.Call[api.Empty](cmd.Context(), layout.SocketPath, "kernel.delete", api.KernelRefParams{Name: args[0]}); err != nil {
				return err
			}
			_, err = fmt.Fprintf(cmd.OutOrStdout(), "removed %s\n", args[0])
			return err
		},
	}
}
