package machine

import (
	"fmt"

	"go.podman.io/podman/v6/pkg/machine/ignition"
	"go.podman.io/podman/v6/pkg/systemd/parser"
)

// fcosDirSymlinks maps FCOS root-level symlinks to their real paths.
// FCOS symlinks several top-level dirs into /var for the read-only rootfs.
// systemd rejects a mount unit whose Where= traverses a symlink, so we
// resolve these before writing the ignition unit.
var fcosDirSymlinks = map[string]string{
	"/home": "/var/home",
	"/mnt":  "/var/mnt",
	"/opt":  "/var/opt",
	"/root": "/var/roothome",
	"/srv":  "/var/srv",
}

// canonicalizeFCOSMountTarget returns the canonical path for target by
// substituting any known FCOS root-level symlink prefix.
func canonicalizeFCOSMountTarget(target string) string {
	// Guard check for empty strings or paths not starting with '/'
	if len(target) < 2 || target[0] != '/' {
		return target
	}

	// Find the end of the first path component (e.g. "/home" in "/home/alice").
	end := 1
	for end < len(target) && target[end] != '/' {
		end++
	}

	if real, ok := fcosDirSymlinks[target[:end]]; ok {
		return real + target[end:]
	}
	return target
}

// GenerateSystemDFilesForVirtiofsMounts generates the systemd unit files needed
// to mount virtiofs volumes inside a FCOS guest VM. It is shared between the
// AppleHV, LibKrun, and QEMU providers.
//
// Mounting in FCOS with virtiofs is a bit of a dance.  we need a unit file for
// the mount, a unit file for automatic mounting on boot, and a "preparatory"
// service file that disables FCOS security, performs the mkdir of the mount
// point, and then re-enables security. This must be done for each mount.
func GenerateSystemDFilesForVirtiofsMounts(mounts []VirtIoFs) ([]ignition.Unit, error) {
	unitFiles := make([]ignition.Unit, 0, len(mounts)+2)
	for _, mnt := range mounts {
		// Create mount unit for each mount
		mountUnit := parser.NewUnitFile()
		mountUnit.Add("Mount", "What", "%s")
		mountUnit.Add("Mount", "Where", "%s")
		mountUnit.Add("Mount", "Type", "virtiofs")
		mountUnit.Add("Mount", "Options", fmt.Sprintf("context=\"%s\"", NFSSELinuxContext))
		mountUnit.Add("Install", "WantedBy", "local-fs.target")
		mountUnitFile, err := mountUnit.ToString()
		if err != nil {
			return nil, err
		}

		// Use the canonical path so systemd accepts the Where= value.
		// On FCOS /home is a symlink to var/home; systemd rejects non-canonical paths.
		canonicalTarget := canonicalizeFCOSMountTarget(mnt.Target)
		virtiofsMount := ignition.Unit{
			Enabled:  ignition.BoolToPtr(true),
			Name:     fmt.Sprintf("%s.mount", parser.PathEscape(canonicalTarget)),
			Contents: ignition.StrToPtr(fmt.Sprintf(mountUnitFile, mnt.Tag, canonicalTarget)),
		}

		unitFiles = append(unitFiles, virtiofsMount)
	}

	// This is a way to workaround the FCOS limitation of creating directories
	// at the rootfs / and then mounting to them.
	immutableRootOff := parser.NewUnitFile()
	immutableRootOff.Add("Unit", "Description", "Allow systemd to create mount points on /")
	immutableRootOff.Add("Unit", "DefaultDependencies", "no")

	immutableRootOff.Add("Service", "Type", "oneshot")
	immutableRootOff.Add("Service", "ExecStart", "chattr -i /")

	immutableRootOff.Add("Install", "WantedBy", "local-fs-pre.target")
	immutableRootOffFile, err := immutableRootOff.ToString()
	if err != nil {
		return nil, err
	}

	immutableRootOffUnit := ignition.Unit{
		Contents: ignition.StrToPtr(immutableRootOffFile),
		Name:     "immutable-root-off.service",
		Enabled:  ignition.BoolToPtr(true),
	}
	unitFiles = append(unitFiles, immutableRootOffUnit)

	immutableRootOn := parser.NewUnitFile()
	immutableRootOn.Add("Unit", "Description", "Set / back to immutable after mounts are done")
	immutableRootOn.Add("Unit", "DefaultDependencies", "no")
	immutableRootOn.Add("Unit", "After", "local-fs.target")

	immutableRootOn.Add("Service", "Type", "oneshot")
	immutableRootOn.Add("Service", "ExecStart", "chattr +i /")

	immutableRootOn.Add("Install", "WantedBy", "local-fs.target")
	immutableRootOnFile, err := immutableRootOn.ToString()
	if err != nil {
		return nil, err
	}

	immutableRootOnUnit := ignition.Unit{
		Contents: ignition.StrToPtr(immutableRootOnFile),
		Name:     "immutable-root-on.service",
		Enabled:  ignition.BoolToPtr(true),
	}
	unitFiles = append(unitFiles, immutableRootOnUnit)

	return unitFiles, nil
}
