package runtime

import (
	"context"
	"fmt"
	"strings"

	"git.cloudinit.dev/coreci/orca/internal/sshpush"
)

// vmidFor returns a deterministic 5-digit VMID derived from the alloc
// ID (hash(alloc.ID) % 99999 + 1). Used by both pveVMRuntime and
// pveCTRuntime — VM and container IDs share the same numeric space on
// a Proxmox node, but the namespace is sparse (one alloc = one VMID)
// so collisions are rare in practice.
func vmidFor(alloc *Alloc) int {
	id := allocIDHash(alloc.ID)
	if id < 100 {
		id += 100
	}
	return id
}

// PveVMRuntime implements Runtime for the "pve-vm" one_of using the
// `qm` tool over SSH on a Proxmox peer (extends REQ-076). The transport
// is injected.
//
// Lifecycle:
//
//   - Prepare: `qm create <vmid> --memory <mb> --cores <n> --scsi0 <disk>`
//   - Start:   `qm start <vmid>`
//   - Stop:    `qm shutdown <vmid>` (graceful) then `qm stop <vmid>` (force)
//   - Status:  `qm status <vmid>`
type PveVMRuntime struct {
	transport *sshpush.Transport
}

// NewPveVMRuntime returns a PveVMRuntime backed by the given transport.
func NewPveVMRuntime(t *sshpush.Transport) *PveVMRuntime {
	return &PveVMRuntime{transport: t}
}

// Prepare creates the VM. Memory/Cores default to 512MB / 1 if the
// spec's Runtime block omits them; the disk is the spec's image field
// (a Proxmox storage path like local:vmdir/disk.qcow2).
func (v *PveVMRuntime) Prepare(ctx context.Context, alloc *Alloc) error {
	if alloc == nil || alloc.Spec == nil || alloc.Spec.Runtime == nil {
		return fmt.Errorf("pve-vm: nil alloc/spec/runtime")
	}
	vmid := vmidFor(alloc)
	image := alloc.Spec.Runtime.Image
	if image == "" {
		return fmt.Errorf("pve-vm: alloc %s has no disk (Runtime.Image)", alloc.ID)
	}
	mb := 512
	cores := 1
	cmd := fmt.Sprintf("qm create %d --memory %d --cores %d --scsi0 %q", vmid, mb, cores, image)
	if _, err := v.transport.Exec(ctx, alloc.Node, cmd); err != nil {
		return fmt.Errorf("pve-vm: create: %w", err)
	}
	return nil
}

// Start boots the VM.
func (v *PveVMRuntime) Start(ctx context.Context, alloc *Alloc) (int, error) {
	vmid := vmidFor(alloc)
	if _, err := v.transport.Exec(ctx, alloc.Node, fmt.Sprintf("qm start %d", vmid)); err != nil {
		return 0, fmt.Errorf("pve-vm: start: %w", err)
	}
	return vmid, nil
}

// Stop gracefully shuts down then force-stops the VM.
func (v *PveVMRuntime) Stop(ctx context.Context, alloc *Alloc) error {
	vmid := vmidFor(alloc)
	if _, err := v.transport.Exec(ctx, alloc.Node, fmt.Sprintf("qm shutdown %d", vmid)); err != nil {
		// Best-effort graceful; fall through to force stop.
	}
	if _, err := v.transport.Exec(ctx, alloc.Node, fmt.Sprintf("qm stop %d", vmid)); err != nil {
		return fmt.Errorf("pve-vm: stop: %w", err)
	}
	return nil
}

// Status reports the VM state from `qm status`.
func (v *PveVMRuntime) Status(ctx context.Context, alloc *Alloc) (State, error) {
	vmid := vmidFor(alloc)
	out, err := v.transport.Exec(ctx, alloc.Node, fmt.Sprintf("qm status %d", vmid))
	if err != nil {
		return StateFailed, fmt.Errorf("pve-vm: status: %w", err)
	}
	s := strings.ToLower(string(out))
	switch {
	case strings.Contains(s, "running"):
		return StateRunning, nil
	case strings.Contains(s, "stopped"):
		return StateStopped, nil
	default:
		return StatePending, nil
	}
}

// PveCTRuntime implements Runtime for the "pve-ct" one_of using the
// `pct` tool over SSH on a Proxmox peer (extends REQ-076).
//
// Lifecycle:
//
//   - Prepare: `pct create <vmid> <template> --memory <mb> --cores <n>`
//   - Start:   `pct start <vmid>`
//   - Stop:    `pct shutdown <vmid>` then `pct stop <vmid>`
//   - Status:  `pct status <vmid>`
type PveCTRuntime struct {
	transport *sshpush.Transport
}

// NewPveCTRuntime returns a PveCTRuntime backed by the given transport.
func NewPveCTRuntime(t *sshpush.Transport) *PveCTRuntime {
	return &PveCTRuntime{transport: t}
}

// Prepare creates the LXC container.
func (c *PveCTRuntime) Prepare(ctx context.Context, alloc *Alloc) error {
	if alloc == nil || alloc.Spec == nil || alloc.Spec.Runtime == nil {
		return fmt.Errorf("pve-ct: nil alloc/spec/runtime")
	}
	vmid := vmidFor(alloc)
	template := alloc.Spec.Runtime.Image
	if template == "" {
		return fmt.Errorf("pve-ct: alloc %s has no template (Runtime.Image)", alloc.ID)
	}
	mb := 512
	cores := 1
	cmd := fmt.Sprintf("pct create %d %q --memory %d --cores %d", vmid, template, mb, cores)
	if _, err := c.transport.Exec(ctx, alloc.Node, cmd); err != nil {
		return fmt.Errorf("pve-ct: create: %w", err)
	}
	return nil
}

// Start boots the container.
func (c *PveCTRuntime) Start(ctx context.Context, alloc *Alloc) (int, error) {
	vmid := vmidFor(alloc)
	if _, err := c.transport.Exec(ctx, alloc.Node, fmt.Sprintf("pct start %d", vmid)); err != nil {
		return 0, fmt.Errorf("pve-ct: start: %w", err)
	}
	return vmid, nil
}

// Stop gracefully then force stops the container.
func (c *PveCTRuntime) Stop(ctx context.Context, alloc *Alloc) error {
	vmid := vmidFor(alloc)
	if _, err := c.transport.Exec(ctx, alloc.Node, fmt.Sprintf("pct shutdown %d", vmid)); err != nil {
		// best-effort
	}
	if _, err := c.transport.Exec(ctx, alloc.Node, fmt.Sprintf("pct stop %d", vmid)); err != nil {
		return fmt.Errorf("pve-ct: stop: %w", err)
	}
	return nil
}

// Status reports the container state from `pct status`.
func (c *PveCTRuntime) Status(ctx context.Context, alloc *Alloc) (State, error) {
	vmid := vmidFor(alloc)
	out, err := c.transport.Exec(ctx, alloc.Node, fmt.Sprintf("pct status %d", vmid))
	if err != nil {
		return StateFailed, fmt.Errorf("pve-ct: status: %w", err)
	}
	s := strings.ToLower(string(out))
	switch {
	case strings.Contains(s, "running"):
		return StateRunning, nil
	case strings.Contains(s, "stopped"):
		return StateStopped, nil
	default:
		return StatePending, nil
	}
}
