// Package imagemgr contains the pure helpers of the banger image subsystem:
// path validators, artifact staging, managed-image metadata, and the guest
// provisioning script generator used by image build.
//
// The orchestrator methods (BuildImage, RegisterImage, PromoteImage,
// DeleteImage) still live in the daemon package and compose these helpers.
package imagemgr

import (
	"context"
	"crypto/sha256"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"banger/internal/system"
)

// debianBasePackages is the apt package list applied by
// `image build --from-image` to Debian-based managed rootfses. Small
// curated set: most of the developer tooling the golden image ships
// lives in the Dockerfile, not here.
var debianBasePackages = []string{
	"make",
	"git",
	"less",
	"tree",
	"ca-certificates",
	"curl",
	"wget",
	"iproute2",
	"vim",
	"tmux",
}

// DebianBasePackages returns a copy of the base package set.
func DebianBasePackages() []string {
	return append([]string(nil), debianBasePackages...)
}

// hashPackages returns the hex sha256 of the package list, used as
// drift-detection metadata alongside a built rootfs.
func hashPackages(lines []string) string {
	sum := sha256.Sum256([]byte(strings.Join(lines, "\n") + "\n"))
	return fmt.Sprintf("%x", sum)
}

// ValidateRegisterPaths checks that rootfs + kernel exist and that optional
// artifacts, when provided, also exist.
func ValidateRegisterPaths(rootfsPath, workSeedPath, kernelPath, initrdPath, modulesDir string) error {
	checks := system.NewPreflight()
	checks.RequireFile(rootfsPath, "rootfs image", `pass --rootfs <path>`)
	if workSeedPath != "" {
		checks.RequireFile(workSeedPath, "work-seed image", `pass --work-seed <path> or rebuild the image with a work seed`)
	}
	addKernelChecks(checks, kernelPath, initrdPath, modulesDir)
	return checks.Err("image register failed")
}

// ValidateKernelPaths checks the kernel triple alone, used by flows
// (e.g. image pull) that produce the rootfs themselves.
func ValidateKernelPaths(kernelPath, initrdPath, modulesDir string) error {
	checks := system.NewPreflight()
	addKernelChecks(checks, kernelPath, initrdPath, modulesDir)
	return checks.Err("kernel preflight failed")
}

func addKernelChecks(checks *system.Preflight, kernelPath, initrdPath, modulesDir string) {
	checks.RequireFile(kernelPath, "kernel image", `pass --kernel <path>`)
	if initrdPath != "" {
		checks.RequireFile(initrdPath, "initrd image", `pass --initrd <path>`)
	}
	if modulesDir != "" {
		checks.RequireDir(modulesDir, "kernel modules dir", `pass --modules <dir>`)
	}
}

// ValidatePromotePaths checks that an existing registered image's artifacts
// are still present before promoting it to daemon-owned storage.
func ValidatePromotePaths(rootfsPath, kernelPath, initrdPath, modulesDir string) error {
	checks := system.NewPreflight()
	checks.RequireFile(rootfsPath, "rootfs image", `re-register the image with a valid rootfs`)
	checks.RequireFile(kernelPath, "kernel image", `re-register the image with a valid kernel`)
	if initrdPath != "" {
		checks.RequireFile(initrdPath, "initrd image", `re-register the image with a valid initrd`)
	}
	if modulesDir != "" {
		checks.RequireDir(modulesDir, "kernel modules dir", `re-register the image with a valid modules dir`)
	}
	return checks.Err("image promote failed")
}

// StageBootArtifacts copies kernel/initrd/modules into artifactDir and
// returns the staged paths. initrd and modules are optional; an empty source
// returns an empty staged path.
func StageBootArtifacts(ctx context.Context, runner system.CommandRunner, artifactDir, kernelSource, initrdSource, modulesSource string) (string, string, string, error) {
	kernelPath := filepath.Join(artifactDir, "kernel")
	if err := system.CopyFilePreferClone(kernelSource, kernelPath); err != nil {
		return "", "", "", err
	}
	initrdPath := ""
	if strings.TrimSpace(initrdSource) != "" {
		initrdPath = filepath.Join(artifactDir, "initrd.img")
		if err := system.CopyFilePreferClone(initrdSource, initrdPath); err != nil {
			return "", "", "", err
		}
	}
	modulesDir := ""
	if strings.TrimSpace(modulesSource) != "" {
		modulesDir = filepath.Join(artifactDir, "modules")
		if err := os.MkdirAll(modulesDir, 0o755); err != nil {
			return "", "", "", err
		}
		if err := system.CopyDirContents(ctx, runner, modulesSource, modulesDir, false); err != nil {
			return "", "", "", err
		}
	}
	return kernelPath, initrdPath, modulesDir, nil
}

// StageOptionalArtifactPath returns the destination path for an optional
// artifact in artifactDir, or "" when stagedPath is empty (artifact absent).
func StageOptionalArtifactPath(artifactDir, stagedPath, name string) string {
	if strings.TrimSpace(stagedPath) == "" {
		return ""
	}
	return filepath.Join(artifactDir, name)
}

// BuildMetadataPackages returns the canonical package set recorded for a
// managed image build.
func BuildMetadataPackages() []string {
	return DebianBasePackages()
}

// WritePackagesMetadata writes the hash of packages next to rootfsPath so
// future builds can detect drift. Empty packages or rootfsPath is a no-op.
func WritePackagesMetadata(rootfsPath string, packages []string) error {
	if rootfsPath == "" || len(packages) == 0 {
		return nil
	}
	metadataPath := rootfsPath + ".packages.sha256"
	return os.WriteFile(metadataPath, []byte(hashPackages(packages)+"\n"), 0o644)
}
