package daemon

import (
	"context"
	"fmt"

	"banger/internal/api"
	"banger/internal/buildinfo"
	"banger/internal/rpc"
)

// handler is the signature every RPC method dispatches through. Keeps
// Daemon.dispatch a one-liner — lookup + invoke — instead of the old
// ~240-line `switch`. Handlers close over a `*Daemon` parameter at
// call time (passed by the driver) rather than baked into the map,
// so tests that stand up a *Daemon with custom wiring re-use the
// same table without re-registering anything.
type handler func(ctx context.Context, d *Daemon, req rpc.Request) rpc.Response

// paramHandler wraps the common "decode params of type P, call
// service returning (R, error), wrap R" flow that 28 of 34 methods
// follow. Compile-time type-safe — no reflection. P and R are
// deduced from the function literal passed in, so per-handler
// registration reads as "what's the RPC shape + what's the service
// call" and nothing else.
func paramHandler[P any, R any](call func(ctx context.Context, d *Daemon, p P) (R, error)) handler {
	return func(ctx context.Context, d *Daemon, req rpc.Request) rpc.Response {
		p, err := rpc.DecodeParams[P](req)
		if err != nil {
			return rpc.NewError("bad_request", err.Error())
		}
		result, err := call(ctx, d, p)
		return marshalResultOrError(result, err)
	}
}

// noParamHandler is the decode-free variant for RPC methods that
// take no params (ping, shutdown, *.list, kernel.catalog).
func noParamHandler[R any](call func(ctx context.Context, d *Daemon) (R, error)) handler {
	return func(ctx context.Context, d *Daemon, _ rpc.Request) rpc.Response {
		result, err := call(ctx, d)
		return marshalResultOrError(result, err)
	}
}

// rpcHandlers maps every supported method name to its handler. Adding
// or removing a method is a single-line diff here — unlike the old
// switch, there's no four-line decode/call/wrap boilerplate to copy.
// The four special-case handlers (vm.logs, vm.ssh, ping, shutdown)
// live below the map; they need pre-service validation or raw result
// encoding that the generic wrapper can't express.
var rpcHandlers = map[string]handler{
	"ping":                   pingHandler,
	"shutdown":               shutdownHandler,
	"daemon.operations.list": noParamHandler(daemonOperationsListDispatch),

	"vm.create":        paramHandler(vmCreateDispatch),
	"vm.create.begin":  paramHandler(vmCreateBeginDispatch),
	"vm.create.status": paramHandler(vmCreateStatusDispatch),
	"vm.create.cancel": paramHandler(vmCreateCancelDispatch),
	"vm.list":          noParamHandler(vmListDispatch),
	"vm.show":          paramHandler(vmShowDispatch),
	"vm.start":         paramHandler(vmStartDispatch),
	"vm.stop":          paramHandler(vmStopDispatch),
	"vm.kill":          paramHandler(vmKillDispatch),
	"vm.restart":       paramHandler(vmRestartDispatch),
	"vm.delete":        paramHandler(vmDeleteDispatch),
	"vm.set":           paramHandler(vmSetDispatch),
	"vm.stats":         paramHandler(vmStatsDispatch),
	"vm.logs":          vmLogsHandler,
	"vm.ssh":           vmSSHHandler,
	"vm.health":        paramHandler(vmHealthDispatch),
	"vm.ping":          paramHandler(vmPingDispatch),
	"vm.ports":         paramHandler(vmPortsDispatch),

	"vm.workspace.prepare": paramHandler(workspacePrepareDispatch),
	"vm.workspace.export":  paramHandler(workspaceExportDispatch),

	"image.list":        noParamHandler(imageListDispatch),
	"image.show":        paramHandler(imageShowDispatch),
	"image.register":    paramHandler(imageRegisterDispatch),
	"image.promote":     paramHandler(imagePromoteDispatch),
	"image.delete":      paramHandler(imageDeleteDispatch),
	"image.pull":        paramHandler(imagePullDispatch),
	"image.cache.prune": paramHandler(imageCachePruneDispatch),

	"kernel.list":    noParamHandler(kernelListDispatch),
	"kernel.show":    paramHandler(kernelShowDispatch),
	"kernel.delete":  paramHandler(kernelDeleteDispatch),
	"kernel.import":  paramHandler(kernelImportDispatch),
	"kernel.pull":    paramHandler(kernelPullDispatch),
	"kernel.catalog": noParamHandler(kernelCatalogDispatch),
}

// ---- Service-call adapters (kept thin; the interesting shape is up
// ---- in the `paramHandler` generic. These exist so the map entries
// ---- stay readable at a glance.)

func vmCreateDispatch(ctx context.Context, d *Daemon, p api.VMCreateParams) (api.VMShowResult, error) {
	vm, err := d.vm.CreateVM(ctx, p)
	return api.VMShowResult{VM: vm}, err
}

func vmCreateBeginDispatch(ctx context.Context, d *Daemon, p api.VMCreateParams) (api.VMCreateBeginResult, error) {
	op, err := d.vm.BeginVMCreate(ctx, p)
	return api.VMCreateBeginResult{Operation: op}, err
}

func vmCreateStatusDispatch(ctx context.Context, d *Daemon, p api.VMCreateStatusParams) (api.VMCreateStatusResult, error) {
	op, err := d.vm.VMCreateStatus(ctx, p.ID)
	return api.VMCreateStatusResult{Operation: op}, err
}

func vmCreateCancelDispatch(ctx context.Context, d *Daemon, p api.VMCreateStatusParams) (api.Empty, error) {
	return api.Empty{}, d.vm.CancelVMCreate(ctx, p.ID)
}

func vmListDispatch(ctx context.Context, d *Daemon) (api.VMListResult, error) {
	vms, err := d.store.ListVMs(ctx)
	return api.VMListResult{VMs: vms}, err
}

func vmShowDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMShowResult, error) {
	vm, err := d.vm.FindVM(ctx, p.IDOrName)
	return api.VMShowResult{VM: vm}, err
}

func vmStartDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMShowResult, error) {
	vm, err := d.vm.StartVM(ctx, p.IDOrName)
	return api.VMShowResult{VM: vm}, err
}

func vmStopDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMShowResult, error) {
	vm, err := d.vm.StopVM(ctx, p.IDOrName)
	return api.VMShowResult{VM: vm}, err
}

func vmKillDispatch(ctx context.Context, d *Daemon, p api.VMKillParams) (api.VMShowResult, error) {
	vm, err := d.vm.KillVM(ctx, p)
	return api.VMShowResult{VM: vm}, err
}

func vmRestartDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMShowResult, error) {
	vm, err := d.vm.RestartVM(ctx, p.IDOrName)
	return api.VMShowResult{VM: vm}, err
}

func vmDeleteDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMShowResult, error) {
	vm, err := d.vm.DeleteVM(ctx, p.IDOrName)
	return api.VMShowResult{VM: vm}, err
}

func vmSetDispatch(ctx context.Context, d *Daemon, p api.VMSetParams) (api.VMShowResult, error) {
	vm, err := d.vm.SetVM(ctx, p)
	return api.VMShowResult{VM: vm}, err
}

func vmStatsDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMStatsResult, error) {
	vm, stats, err := d.stats.GetVMStats(ctx, p.IDOrName)
	return api.VMStatsResult{VM: vm, Stats: stats}, err
}

func vmHealthDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMHealthResult, error) {
	return d.stats.HealthVM(ctx, p.IDOrName)
}

func vmPingDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMPingResult, error) {
	return d.stats.PingVM(ctx, p.IDOrName)
}

func vmPortsDispatch(ctx context.Context, d *Daemon, p api.VMRefParams) (api.VMPortsResult, error) {
	return d.stats.PortsVM(ctx, p.IDOrName)
}

func workspacePrepareDispatch(ctx context.Context, d *Daemon, p api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
	ws, err := d.ws.PrepareVMWorkspace(ctx, p)
	return api.VMWorkspacePrepareResult{Workspace: ws}, err
}

func workspaceExportDispatch(ctx context.Context, d *Daemon, p api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
	return d.ws.ExportVMWorkspace(ctx, p)
}

func imageListDispatch(ctx context.Context, d *Daemon) (api.ImageListResult, error) {
	images, err := d.store.ListImages(ctx)
	return api.ImageListResult{Images: images}, err
}

func imageShowDispatch(ctx context.Context, d *Daemon, p api.ImageRefParams) (api.ImageShowResult, error) {
	image, err := d.img.FindImage(ctx, p.IDOrName)
	return api.ImageShowResult{Image: image}, err
}

func imageRegisterDispatch(ctx context.Context, d *Daemon, p api.ImageRegisterParams) (api.ImageShowResult, error) {
	image, err := d.img.RegisterImage(ctx, p)
	return api.ImageShowResult{Image: image}, err
}

func imagePromoteDispatch(ctx context.Context, d *Daemon, p api.ImageRefParams) (api.ImageShowResult, error) {
	image, err := d.img.PromoteImage(ctx, p.IDOrName)
	return api.ImageShowResult{Image: image}, err
}

func imageDeleteDispatch(ctx context.Context, d *Daemon, p api.ImageRefParams) (api.ImageShowResult, error) {
	image, err := d.img.DeleteImage(ctx, p.IDOrName)
	return api.ImageShowResult{Image: image}, err
}

func imagePullDispatch(ctx context.Context, d *Daemon, p api.ImagePullParams) (api.ImageShowResult, error) {
	image, err := d.img.PullImage(ctx, p)
	return api.ImageShowResult{Image: image}, err
}

func imageCachePruneDispatch(ctx context.Context, d *Daemon, p api.ImageCachePruneParams) (api.ImageCachePruneResult, error) {
	return d.img.PruneOCICache(ctx, p)
}

func daemonOperationsListDispatch(ctx context.Context, d *Daemon) (api.OperationsListResult, error) {
	return d.ListOperations(ctx)
}

func kernelListDispatch(ctx context.Context, d *Daemon) (api.KernelListResult, error) {
	return d.img.KernelList(ctx)
}

func kernelShowDispatch(ctx context.Context, d *Daemon, p api.KernelRefParams) (api.KernelShowResult, error) {
	entry, err := d.img.KernelShow(ctx, p.Name)
	return api.KernelShowResult{Entry: entry}, err
}

func kernelDeleteDispatch(ctx context.Context, d *Daemon, p api.KernelRefParams) (api.Empty, error) {
	return api.Empty{}, d.img.KernelDelete(ctx, p.Name)
}

func kernelImportDispatch(ctx context.Context, d *Daemon, p api.KernelImportParams) (api.KernelShowResult, error) {
	entry, err := d.img.KernelImport(ctx, p)
	return api.KernelShowResult{Entry: entry}, err
}

func kernelPullDispatch(ctx context.Context, d *Daemon, p api.KernelPullParams) (api.KernelShowResult, error) {
	entry, err := d.img.KernelPull(ctx, p)
	return api.KernelShowResult{Entry: entry}, err
}

func kernelCatalogDispatch(ctx context.Context, d *Daemon) (api.KernelCatalogResult, error) {
	return d.img.KernelCatalog(ctx)
}

// ---- Special-case handlers: pre-service validation, custom error
// ---- codes, or raw rpc.NewResult encoding — things the generic
// ---- wrapper can't express.

// pingHandler is info-only: no service call, just a snapshot of
// build metadata. Raw rpc.NewResult to match the pre-refactor
// encoding; marshalResultOrError would over-wrap this.
func pingHandler(_ context.Context, d *Daemon, _ rpc.Request) rpc.Response {
	info := buildinfo.Current()
	result, _ := rpc.NewResult(api.PingResult{
		Status:  "ok",
		PID:     d.pid,
		Version: info.Version,
		Commit:  info.Commit,
		BuiltAt: info.BuiltAt,
	})
	return result
}

// shutdownHandler triggers async daemon shutdown. `d.Close` runs in
// a goroutine so the RPC response reaches the client before the
// listener closes.
func shutdownHandler(_ context.Context, d *Daemon, _ rpc.Request) rpc.Response {
	go d.Close()
	result, _ := rpc.NewResult(api.ShutdownResult{Status: "stopping"})
	return result
}

// vmLogsHandler needs the "not_found" error code (distinct from
// "operation_failed") when FindVM misses, so the CLI can print a
// cleaner message. The generic paramHandler maps every service err
// to "operation_failed".
func vmLogsHandler(ctx context.Context, d *Daemon, req rpc.Request) rpc.Response {
	params, err := rpc.DecodeParams[api.VMRefParams](req)
	if err != nil {
		return rpc.NewError("bad_request", err.Error())
	}
	vm, err := d.vm.FindVM(ctx, params.IDOrName)
	if err != nil {
		return rpc.NewError("not_found", err.Error())
	}
	return marshalResultOrError(api.VMLogsResult{LogPath: vm.Runtime.LogPath}, nil)
}

// vmSSHHandler does two pre-service validations: FindVM / TouchVM
// for "not_found", then vmAlive for "not_running". Both distinct
// error codes feed cleaner CLI output.
func vmSSHHandler(ctx context.Context, d *Daemon, req rpc.Request) rpc.Response {
	params, err := rpc.DecodeParams[api.VMRefParams](req)
	if err != nil {
		return rpc.NewError("bad_request", err.Error())
	}
	vm, err := d.vm.TouchVM(ctx, params.IDOrName)
	if err != nil {
		return rpc.NewError("not_found", err.Error())
	}
	if !d.vm.vmAlive(vm) {
		return rpc.NewError("not_running", fmt.Sprintf("vm %s is not running", vm.Name))
	}
	return marshalResultOrError(api.VMSSHResult{Name: vm.Name, GuestIP: vm.Runtime.GuestIP}, nil)
}
