package runtime

import (
	"context"
	"fmt"
	"strings"

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

// PodmanRuntime implements Runtime for the "podman" one_of. It runs
// `podman` on the peer over the SSH-push transport (P01). The
// transport is injected via the constructor (dependency injection).
//
// Container lifecycle:
//
//   - Prepare: `podman pull <image>`
//   - Start:   `podman run -d --name orca-<alloc-id> <image> <command>`
//   - Stop:    `podman stop <name>` then `podman rm <name>`
//   - Status:  `podman inspect --format '{{.State.Running}}' <name>`
//
// The container name is `orca-<alloc-id>` (sanitized to lowercase +
// alnum). The runtime keeps no in-process state — each call is a fresh
// SSH exec against the peer.
type PodmanRuntime struct {
	transport *sshpush.Transport
}

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

// containerName returns the deterministic container name for an alloc.
func containerName(alloc *Alloc) string {
	id := strings.ToLower(alloc.ID)
	id = strings.Map(func(r rune) rune {
		if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '-' || r == '_' {
			return r
		}
		return '-'
	}, id)
	return "orca-" + id
}

// Prepare pulls the image on the peer.
func (p *PodmanRuntime) Prepare(ctx context.Context, alloc *Alloc) error {
	image, err := imageFor(alloc)
	if err != nil {
		return err
	}
	cmd := fmt.Sprintf("podman pull %q", image)
	if _, err := p.transport.Exec(ctx, alloc.Node, cmd); err != nil {
		return fmt.Errorf("podman: pull: %w", err)
	}
	return nil
}

// Start runs `podman run -d --name <name> <image> <command>`.
func (p *PodmanRuntime) Start(ctx context.Context, alloc *Alloc) (int, error) {
	image, err := imageFor(alloc)
	if err != nil {
		return 0, err
	}
	cmdStr, _ := commandFor(alloc)
	name := containerName(alloc)
	cmd := fmt.Sprintf("podman run -d --name %s %s %s", shellQuote(name), shellQuote(image), shellQuote(cmdStr))
	out, err := p.transport.Exec(ctx, alloc.Node, cmd)
	if err != nil {
		return 0, fmt.Errorf("podman: run: %w", err)
	}
	// The container ID is the first 12 chars of the printed hash. We
	// don't keep it — Stop/Status use the name — but return a stable
	// synthetic PID derived from the first 4 bytes of the hash for
	// the interface contract.
	cid := strings.TrimSpace(string(out))
	return podmanCidToPID(cid), nil
}

// Stop stops and removes the container.
func (p *PodmanRuntime) Stop(ctx context.Context, alloc *Alloc) error {
	name := containerName(alloc)
	if _, err := p.transport.Exec(ctx, alloc.Node, fmt.Sprintf("podman stop %s", shellQuote(name))); err != nil {
		return fmt.Errorf("podman: stop: %w", err)
	}
	if _, err := p.transport.Exec(ctx, alloc.Node, fmt.Sprintf("podman rm %s", shellQuote(name))); err != nil {
		return fmt.Errorf("podman: rm: %w", err)
	}
	return nil
}

// Status inspects the container's running state.
func (p *PodmanRuntime) Status(ctx context.Context, alloc *Alloc) (State, error) {
	name := containerName(alloc)
	cmd := fmt.Sprintf("podman inspect --format '{{.State.Running}}' %s", shellQuote(name))
	out, err := p.transport.Exec(ctx, alloc.Node, cmd)
	if err != nil {
		return StateFailed, fmt.Errorf("podman: inspect: %w", err)
	}
	v := strings.TrimSpace(string(out))
	switch v {
	case "true":
		return StateRunning, nil
	case "false":
		return StateStopped, nil
	default:
		return StateFailed, fmt.Errorf("podman: unexpected inspect output %q", v)
	}
}

// podmanCidToPID converts a container ID (hex hash) to a positive int
// PID for the interface contract. It reads up to 4 hex chars.
func podmanCidToPID(cid string) int {
	if len(cid) < 1 {
		return 1
	}
	n := len(cid)
	if n > 4 {
		n = 4
	}
	var pid int
	for i := 0; i < n; i++ {
		c := cid[i]
		pid = (pid << 4) | int(hexVal(c))
	}
	if pid <= 0 {
		pid = 1
	}
	return pid
}

func hexVal(c byte) byte {
	switch {
	case c >= '0' && c <= '9':
		return c - '0'
	case c >= 'a' && c <= 'f':
		return c - 'a' + 10
	case c >= 'A' && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}
