package storage

import (
	"context"
	"fmt"
	"strings"
)

// Network-mount guest-reboot / boot reassert (RCA AUDIT-nas-cwa-rca-2026-07-11 fix 1, hardened by
// CAMPAIGN-3 F9/F10/F11).
//
// THE BUG IT FIXES: a fresh guest namespace inherits REAL submounts of the shared parent (ext4, or an
// actively-mounted nfs4/cifs) but NOT an idle autofs trigger — so after any guest reboot an idle NAS
// share silently degrades to a local stub directory inside the guest. The heal is host-side and
// host-global: re-creating the .automount unit emits a FRESH trigger-mount event, which propagates live
// into every running guest's slave bind (live-proven in the RCA remediation, 2026-07-11).
//
// CAMPAIGN-3 hardening:
//   - F11 (read the right unit): the decision is driven ONLY by the host `/proc/mounts` fstype AT the
//     mountpoint — an ACTIVE real mount (nfs4/cifs) is inherited and left alone; anything else is
//     re-armed. The `.automount` unit's own state is NEVER consulted (an armed trigger always reports
//     "active", which is exactly why a state-of-the-automount check mis-skips idle triggers).
//   - F10 (re-arm for real): a `.mount`/`.automount` left in `failed`/start-limit-hit state (the
//     campaign's unexport→idle-timeout→access×5 sequence) is `reset-failed` FIRST — without it the
//     `enable --now` below is refused by the start limit and the share stays dead across every boot.
//   - F9 (say what you did): the pass enumerates by the marker-owned unit files on disk (not by
//     enablement or runtime state) and logs an INFO verdict line for EVERY share — an empty-looking
//     sweep over N shares is structurally impossible.
//
// The action uses the sudoers-granted verbs only (`systemctl reset-failed -- *`, `systemctl stop -- *`,
// `systemctl enable --now -- *`). Idempotent: re-arming an already-armed trigger recreates it — same end
// state, and the fresh mount event is harmless. An ACTIVE real mount is never touched.

// Reassert actions (the §8 decision table, encoded — CAMPAIGN-3 F11).
const (
	// NetReassertRearmed: the trigger was re-created (stop + enable --now) — the propagation heal.
	NetReassertRearmed = "rearmed"
	// NetReassertResetRearmed: the unit was failed/start-limited, reset-failed, THEN re-armed (F10).
	NetReassertResetRearmed = "reset-failed+rearmed"
	// NetReassertSkipActive: a real network fs is mounted at the path — inherited by fresh namespaces,
	// nothing to do (verify only).
	NetReassertSkipActive = "skip-active"
	// NetReassertSkipForeign: a non-network, non-autofs filesystem occupies the path (ext4/tmpfs/…) —
	// not a state this reconcile owns, and re-arming over it would fail (mountpoint busy).
	NetReassertSkipForeign = "skip-foreign"
)

// NetReassertResult is one share's outcome in a reassert pass.
type NetReassertResult struct {
	Name   string // share name (mount dir basename)
	Where  string // guest-visible mountpoint (/mnt/felhom-drives/<name>)
	Action string // NetReassert* constant
	Err    error  // set when the rearm action failed (skip rows never error)
}

// Remediates reports whether an action expects the share to become visible in running guests (drives
// the caller's guest-visibility verify). Skip-active also expects visibility (an inherited live mount);
// only foreign-fs and errored rows expect nothing.
func (r NetReassertResult) Remediates() bool {
	return r.Err == nil && r.Action != NetReassertSkipForeign
}

// netReassertActive reports whether the fstype at a share's mountpoint is a live network mount — the
// ONLY input to the skip-active decision (F11: never the automount unit's state). "" (a failed/disarmed
// automount leaves NO /proc/mounts entry) and "autofs" (an armed-but-idle trigger) are BOTH not-active
// and therefore re-arm targets; a foreign local fs is left alone.
func netReassertClassify(fstype string) string {
	switch {
	case isNetworkMounted(fstype):
		return NetReassertSkipActive
	case fstype == "" || fstype == "autofs":
		// Not actively mounted, but a marker unit exists for this path: idle-armed, disarmed, OR
		// failed/start-limited — all of them must be re-armed so a fresh trigger event propagates.
		return NetReassertRearmed
	default:
		return NetReassertSkipForeign // ext4/tmpfs/… — foreign, not ours to churn
	}
}

// ReassertNetworkAutomounts runs the reassert pass over every configured network mount: for each
// installed pair, decide per netReassertClassify and re-arm every not-active trigger (reset-failed first
// if the unit is stuck). Returns one result per share so callers (agent startup / guest-hook post-start)
// can verify guest visibility. Errors on one share never stop the pass. Callers MUST NOT invoke this
// from periodic health paths — an idle trigger is healthy, and the pass is only needed after a guest
// (re)start or at agent startup.
func (h *SudoHostOps) ReassertNetworkAutomounts(ctx context.Context) []NetReassertResult {
	entries, err := h.networkUnitEntries()
	if err != nil {
		h.logger.Warn("netreassert: unit enumeration failed", "err", err)
		return nil
	}
	fstypes := map[string]string{}
	if h.host != nil {
		if mounts, merr := h.host.Mounts(); merr == nil {
			for _, m := range mounts {
				fstypes[m.MountPoint] = m.FSType
			}
		} else {
			h.logger.Warn("netreassert: mount-table read failed — treating all shares as unmounted", "err", merr)
		}
	}
	var out []NetReassertResult
	for _, e := range entries {
		res := NetReassertResult{Name: e.name, Where: e.where, Action: netReassertClassify(fstypes[e.where])}
		switch res.Action {
		case NetReassertSkipActive:
			h.logger.Info("netreassert: share actively mounted — skip (fresh namespaces inherit real mounts)",
				"name", e.name, "where", e.where, "verdict", res.Action)
		case NetReassertSkipForeign:
			h.logger.Info("netreassert: foreign filesystem at mountpoint — skip (not a network state we own)",
				"name", e.name, "where", e.where, "verdict", res.Action)
		case NetReassertRearmed:
			// F10: clear a failed/start-limit lockout FIRST or the enable --now is refused; the verdict
			// records whether a reset was actually needed.
			if h.resetNetworkAutomountIfFailed(ctx, e.where) {
				res.Action = NetReassertResetRearmed
			}
			if err := h.rearmNetworkAutomount(ctx, e.where); err != nil {
				res.Err = err
				h.logger.Warn("netreassert: trigger re-arm failed", "name", e.name, "where", e.where,
					"verdict", "error", "err", err)
			} else {
				h.logger.Info("netreassert: automount trigger re-armed (fresh mount event propagates into running guests)",
					"name", e.name, "where", e.where, "verdict", res.Action)
			}
		}
		out = append(out, res)
	}
	return out
}

// resetNetworkAutomountIfFailed clears a failed/start-limit-hit lockout on the share's unit pair so the
// subsequent `enable --now` is not refused (F10 — the campaign's start-limited automount that no
// platform path re-armed). Returns true when either unit was in the failed state (so the caller can
// report the reset-failed+rearmed verdict). The failed-state read is unprivileged (`systemctl
// is-failed`, seam-injected); the reset-failed is the new sudoers verb.
func (h *SudoHostOps) resetNetworkAutomountIfFailed(ctx context.Context, where string) bool {
	mountUnit, err := UnitNameForMount(where)
	if err != nil {
		return false
	}
	automountUnit := strings.TrimSuffix(mountUnit, ".mount") + ".automount"
	reset := false
	for _, unit := range []string{automountUnit, mountUnit} {
		if !h.unitFailed(ctx, unit) {
			continue
		}
		reset = true
		if err := h.run(ctx, h.bins.Systemctl, "reset-failed", "--", unit); err != nil {
			// Tolerated: a reset-failed that itself fails still lets the enable --now try; log it.
			h.logger.Warn("netreassert: reset-failed tolerated failure", "unit", unit, "err", err)
		} else {
			h.logger.Warn("netreassert: cleared failed/start-limit lockout before re-arm (F10)", "unit", unit)
		}
	}
	return reset
}

// RearmNetworkAutomount is the caller-driven remediation (CAMPAIGN-3 F11, matrix-corrected): when a
// running guest does NOT see a share after the reassert pass, re-arm its trigger regardless of the
// host's mount state. LIVE FINDING (matrix row 1/2, 2026-07-12): a freshly-rebooted guest does NOT
// inherit an ACTIVE host mount either — only a fresh trigger-mount event propagates into the new
// namespace — so skip-active is safe to skip PROACTIVELY (don't churn shares guests already see) but
// must be remediated REACTIVELY when a specific guest is blind. reset-failed first (F10), then re-arm.
func (h *SudoHostOps) RearmNetworkAutomount(ctx context.Context, where string) error {
	h.resetNetworkAutomountIfFailed(ctx, where)
	return h.rearmNetworkAutomount(ctx, where)
}

// rearmNetworkAutomount stops then re-enables+starts the .automount for a mountpoint. The stop is
// tolerated failing (unit not loaded); the enable --now is the action that must succeed. Both verbs
// are the existing FELHOM_NETMOUNT sudoers grants.
func (h *SudoHostOps) rearmNetworkAutomount(ctx context.Context, where string) error {
	mountUnit, err := UnitNameForMount(where)
	if err != nil {
		return fmt.Errorf("netreassert: unit name for %s: %w", where, err)
	}
	automountUnit := strings.TrimSuffix(mountUnit, ".mount") + ".automount"
	if err := h.run(ctx, h.bins.Systemctl, "stop", "--", automountUnit); err != nil {
		// Tolerated: a not-loaded unit still enables cleanly below.
		h.logger.Debug("netreassert: automount stop tolerated failure", "unit", automountUnit, "err", err)
	}
	if err := h.run(ctx, h.bins.Systemctl, "enable", "--now", "--", automountUnit); err != nil {
		return fmt.Errorf("netreassert: enabling automount %s: %w", automountUnit, err)
	}
	return nil
}
