package localapi

import (
	"context"
	"fmt"

	"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
)

// antiRetargetResolve mirrors signedjobs.WipeExecutor.Execute's anti-retarget
// sequence for the inline customer-confirmed wipe path [AGENT-001].
//
// The bug: handleDiskFormat inspected and gate-bound the device by its durable
// id but then formatted the mutable caller-supplied /dev path (req.Device). If a
// USB re-enumeration reassigned that /dev node to a DIFFERENT physical disk
// between inspection and mkfs, the wipe hit the wrong drive — the classic
// classify→mkfs TOCTOU the signed-jobs path already guards against.
//
// The fix: resolve the confirmed durable id to the CURRENT device, re-derive the
// device's durable id and require an exact match (a path now pointing at a
// different disk derives a different id → refuse), then re-inspect and require it
// to STILL be data-bearing. Returns the re-resolved device to format — never the
// caller-supplied path. resolve/derive/inspect are injected so this is unit
// testable without touching real /dev (the Server wires the real storage funcs).
func antiRetargetResolve(
	durableID string,
	resolve func(string) (string, error),
	derive func(string) (string, error),
	inspect func(string) (storage.DeviceProbe, error),
) (string, error) {
	return antiRetargetResolveExpect(durableID, true, resolve, derive, inspect)
}

// antiRetargetResolveBlank is the BLANK-format sibling [audit D3, AGENT-001's benign-branch twin]: the
// same resolve → re-derive-match → re-inspect sequence, but it requires the re-inspected device to
// STILL be blank (!DataBearing). The blank branch authorized mkfs precisely because the agent read the
// device as having nothing to destroy; if the /dev node was reassigned in the window and now holds
// data, the target changed — refuse rather than format it with neither a customer confirm nor a bound
// durable id.
func antiRetargetResolveBlank(
	durableID string,
	resolve func(string) (string, error),
	derive func(string) (string, error),
	inspect func(string) (storage.DeviceProbe, error),
) (string, error) {
	return antiRetargetResolveExpect(durableID, false, resolve, derive, inspect)
}

// antiRetargetResolveExpect is the shared core: resolve the bound durable id to the CURRENT device,
// re-derive the device's durable id and require an exact match, then re-inspect and require the
// data-bearing state to still be what the caller authorized (expectDataBearing). Returns the
// re-resolved device to format — never a caller-supplied path.
func antiRetargetResolveExpect(
	durableID string,
	expectDataBearing bool,
	resolve func(string) (string, error),
	derive func(string) (string, error),
	inspect func(string) (storage.DeviceProbe, error),
) (string, error) {
	if durableID == "" {
		// A path-only binding is exactly what the durable id exists to prevent.
		return "", fmt.Errorf("no durable_id — refusing a path-only wipe binding")
	}
	device, err := resolve(durableID)
	if err != nil {
		return "", fmt.Errorf("durable id %q no longer resolves (device removed/replaced?) — refusing: %w", durableID, err)
	}
	got, err := derive(device)
	if err != nil {
		return "", fmt.Errorf("cannot re-derive durable id for %s — refusing: %w", device, err)
	}
	if got != durableID {
		return "", fmt.Errorf("durable-id mismatch — %s now has id %q, confirmed id was %q — refusing", device, got, durableID)
	}
	probe, err := inspect(device)
	if err != nil {
		return "", fmt.Errorf("re-inspect %s failed — refusing: %w", device, err)
	}
	if !probe.Probed {
		return "", fmt.Errorf("%s did not probe cleanly at execution — refusing", device)
	}
	if expectDataBearing && !probe.DataBearing() {
		// The customer confirmed wiping a DATA-BEARING device; if it is now blank,
		// the target changed since confirmation — refuse rather than wipe blindly.
		return "", fmt.Errorf("%s is no longer data-bearing (target changed since confirmation) — refusing", device)
	}
	if !expectDataBearing && probe.DataBearing() {
		// The agent authorized a BLANK format; the device is now data-bearing —
		// the target changed since inspection (D3 re-enumeration race) — refuse.
		return "", fmt.Errorf("%s is now data-bearing (target changed since blank inspection) — refusing", device)
	}
	return device, nil
}

// reresolveDurableForWipe wires antiRetargetResolve with the real storage funcs
// and the server's disk inspector. Returns the device to format, or an error to
// refuse on.
func (s *Server) reresolveDurableForWipe(ctx context.Context, durableID string) (string, error) {
	return antiRetargetResolve(
		durableID,
		storage.ResolveDurableDevice,
		storage.DeviceDurableID,
		func(dev string) (storage.DeviceProbe, error) { return s.disks.InspectDevice(ctx, dev) },
	)
}

// reresolveDurableForBlankFormat wires antiRetargetResolveBlank with the real storage funcs and the
// server's disk inspector (audit D3 — the blank-format branch's pre-mkfs anti-retarget re-check).
func (s *Server) reresolveDurableForBlankFormat(ctx context.Context, durableID string) (string, error) {
	return antiRetargetResolveBlank(
		durableID,
		storage.ResolveDurableDevice,
		storage.DeviceDurableID,
		func(dev string) (storage.DeviceProbe, error) { return s.disks.InspectDevice(ctx, dev) },
	)
}
