package signedjobs

import (
	"context"
	"encoding/json"
	"errors"
	"testing"

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

// fakeWipeOps records Format calls and returns a configurable InspectDevice probe.
type fakeWipeOps struct {
	probe       storage.DeviceProbe
	inspectErr  error
	formatCalls []string
}

func (f *fakeWipeOps) InspectDevice(_ context.Context, device string) (storage.DeviceProbe, error) {
	p := f.probe
	p.Device = device
	return p, f.inspectErr
}
func (f *fakeWipeOps) Format(_ context.Context, device, _ string) error {
	f.formatCalls = append(f.formatCalls, device)
	return nil
}

// newWipeExec builds a WipeExecutor with injected resolve/derive (so durable resolution is
// deterministic in a unit test, no real /dev needed).
func newWipeExec(ops WipeOps, resolve func(string) (string, error), derive func(string) (string, error)) *WipeExecutor {
	w := NewWipeExecutor(ops, quiet())
	w.resolve = resolve
	w.derive = derive
	return w
}

const durable = "byid:wwn-0xtest"

func params(t *testing.T, durableID, fstype string) json.RawMessage {
	t.Helper()
	b, _ := json.Marshal(map[string]string{"durable_id": durableID, "fstype": fstype})
	return b
}

// VALID: durable resolves, re-derives to the same id, device is data-bearing → Format runs.
func TestWipe_ValidExecutes(t *testing.T) {
	ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4"}}
	w := newWipeExec(ops,
		func(id string) (string, error) { return "/dev/sdb", nil },
		func(dev string) (string, error) { return durable, nil })
	if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err != nil {
		t.Fatalf("valid wipe: %v", err)
	}
	if len(ops.formatCalls) != 1 || ops.formatCalls[0] != "/dev/sdb" {
		t.Fatalf("Format calls = %v, want one on /dev/sdb", ops.formatCalls)
	}
}

// PATH-ONLY (no durable_id) → refused, Format NOT called.
func TestWipe_PathOnlyRefused(t *testing.T) {
	ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}}
	w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil })
	// params carry a raw device path, NOT a durable id.
	p, _ := json.Marshal(map[string]string{"device": "/dev/sdb", "fstype": "ext4"})
	if err := w.Execute(context.Background(), "storage_wipe", p); err == nil {
		t.Fatal("path-only wipe must be refused")
	}
	if len(ops.formatCalls) != 0 {
		t.Errorf("Format was called on a path-only binding: %v", ops.formatCalls)
	}
}

// DURABLE MISMATCH: the resolved device re-derives to a DIFFERENT id → refused (anti-retarget).
func TestWipe_DurableMismatchRefused(t *testing.T) {
	ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}}
	w := newWipeExec(ops,
		func(string) (string, error) { return "/dev/sdb", nil },
		func(string) (string, error) { return "byid:wwn-0xDIFFERENT", nil }) // re-derive disagrees
	if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil {
		t.Fatal("a durable-id mismatch must be refused")
	}
	if len(ops.formatCalls) != 0 {
		t.Errorf("Format was called despite a durable-id mismatch: %v", ops.formatCalls)
	}
}

// DEVICE GONE: the durable id no longer resolves → refused (device removed/replaced).
func TestWipe_ResolveFailureRefused(t *testing.T) {
	ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}}
	w := newWipeExec(ops,
		func(string) (string, error) { return "", errors.New("no such by-id link") },
		func(string) (string, error) { return durable, nil })
	if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil {
		t.Fatal("an unresolvable durable id must be refused")
	}
	if len(ops.formatCalls) != 0 {
		t.Errorf("Format was called on an unresolvable device: %v", ops.formatCalls)
	}
}

// RE-INSPECT: the resolved device is no longer data-bearing (target changed) → refused.
func TestWipe_ReinspectNonDataBearingRefused(t *testing.T) {
	ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true /* blank: no fs/parts */}}
	w := newWipeExec(ops,
		func(string) (string, error) { return "/dev/sdb", nil },
		func(string) (string, error) { return durable, nil })
	if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil {
		t.Fatal("a device that is no longer data-bearing must be refused (target changed)")
	}
	if len(ops.formatCalls) != 0 {
		t.Errorf("Format was called on a non-target device: %v", ops.formatCalls)
	}
}

// RE-INSPECT: the device did not probe cleanly → refused (fail-safe).
func TestWipe_NotProbedRefused(t *testing.T) {
	ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: false}}
	w := newWipeExec(ops,
		func(string) (string, error) { return "/dev/sdb", nil },
		func(string) (string, error) { return durable, nil })
	if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil {
		t.Fatal("a device that did not probe cleanly must be refused")
	}
	if len(ops.formatCalls) != 0 {
		t.Errorf("Format was called on an unprobed device: %v", ops.formatCalls)
	}
}

// A non-wipe op → ErrNoExecutor (left for the owning slice; Format not called).
func TestWipe_OtherOpIsNoExecutor(t *testing.T) {
	ops := &fakeWipeOps{}
	w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil })
	if err := w.Execute(context.Background(), "guest_destroy", params(t, durable, "ext4")); !errors.Is(err, ErrNoExecutor) {
		t.Fatalf("guest_destroy err = %v, want ErrNoExecutor", err)
	}
}
