package workertest

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"testing"
	"time"
)

// Probe is injected into real domain transition boundaries.
type Probe func(CrashPoint) error

// DurableCriticalCase owns one fresh durable-store fixture for one crash
// point. RunUntilCrash must stop without normal cleanup when Probe returns
// ErrInjectedCrash. The harness calls NewWorker twice with the exact same store
// fixture, requires distinct concrete worker pointers, and passes those workers
// to the crash and restart callbacks. AssertRecovered checks durable state and
// effects through the same store fixture.
type DurableCriticalCase[Store any, Worker any] struct {
	DurableStore    Store
	NewWorker       func(context.Context, Store) (Worker, error)
	RunUntilCrash   func(context.Context, Worker, Probe) error
	RestartAndDrain func(context.Context, Worker) error
	AssertRecovered func(context.Context, Store) error
}

// RunDurableCriticalCrashMatrix executes every common crash boundary. The
// harness proves coverage and restart sequencing; the domain assertion remains
// responsible for exact acceptance, effect, disposition, and replay semantics.
func RunDurableCriticalCrashMatrix[Store any, Worker any](
	t *testing.T,
	timeout time.Duration,
	factory func(*testing.T, CrashPoint) DurableCriticalCase[Store, Worker],
) {
	t.Helper()
	if timeout <= 0 {
		t.Fatal("worker conformance: positive case timeout is required")
	}
	if factory == nil {
		t.Fatal("worker conformance: case factory is required")
	}
	for _, point := range DurableCriticalCrashPoints() {
		point := point
		t.Run(string(point), func(t *testing.T) {
			caseUnderTest := factory(t, point)
			if caseUnderTest.NewWorker == nil || caseUnderTest.RunUntilCrash == nil ||
				caseUnderTest.RestartAndDrain == nil || caseUnderTest.AssertRecovered == nil {
				t.Fatal("worker conformance: case is incomplete")
			}
			if _, err := referenceIdentity("durable store fixture", caseUnderTest.DurableStore); err != nil {
				t.Fatal(err)
			}

			initialCtx, cancelInitial := context.WithTimeout(context.Background(), timeout)
			type workerResult struct {
				worker Worker
				err    error
			}
			initial, waitErr := runBeforeDeadline(initialCtx, func() workerResult {
				worker, workerErr := caseUnderTest.NewWorker(initialCtx, caseUnderTest.DurableStore)
				return workerResult{worker: worker, err: workerErr}
			})
			cancelInitial()
			if waitErr != nil {
				t.Fatalf("worker conformance: initial worker construction before %s timed out: %v", point, waitErr)
			}
			if initial.err != nil {
				t.Fatalf("worker conformance: initial worker construction before %s: %v", point, initial.err)
			}
			if _, err := referenceIdentity("initial worker", initial.worker); err != nil {
				t.Fatal(err)
			}

			injector, err := NewInjector(point)
			if err != nil {
				t.Fatal(err)
			}
			crashCtx, cancelCrash := context.WithTimeout(context.Background(), timeout)
			crashErr, crashWaitErr := runBeforeDeadline(crashCtx, func() error {
				return caseUnderTest.RunUntilCrash(crashCtx, initial.worker, injector.Probe)
			})
			cancelCrash()
			if crashWaitErr != nil {
				t.Fatalf("worker conformance: crash boundary %s timed out: %v", point, crashWaitErr)
			}
			if !errors.Is(crashErr, ErrInjectedCrash) || !injector.Triggered() {
				t.Fatalf("worker conformance: crash boundary %s was not reached: %v", point, crashErr)
			}

			restartCtx, cancelRestart := context.WithTimeout(context.Background(), timeout)
			restarted, waitErr := runBeforeDeadline(restartCtx, func() workerResult {
				worker, restartErr := caseUnderTest.NewWorker(restartCtx, caseUnderTest.DurableStore)
				if restartErr == nil {
					restartErr = validateFreshWorker(initial.worker, worker)
				}
				if restartErr == nil {
					restartErr = caseUnderTest.RestartAndDrain(restartCtx, worker)
				}
				return workerResult{worker: worker, err: restartErr}
			})
			cancelRestart()
			if waitErr != nil {
				t.Fatalf("worker conformance: restart after %s timed out: %v", point, waitErr)
			}
			if restarted.err != nil {
				t.Fatalf("worker conformance: restart after %s: %v", point, restarted.err)
			}
			assertCtx, cancelAssert := context.WithTimeout(context.Background(), timeout)
			assertErr, assertWaitErr := runBeforeDeadline(assertCtx, func() error {
				return caseUnderTest.AssertRecovered(assertCtx, caseUnderTest.DurableStore)
			})
			cancelAssert()
			if assertWaitErr != nil {
				t.Fatalf("worker conformance: recovery assertion after %s timed out: %v", point, assertWaitErr)
			}
			if assertErr != nil {
				t.Fatalf("worker conformance: recovery assertion after %s: %v", point, assertErr)
			}
		})
	}
}

func runBeforeDeadline[T any](ctx context.Context, callback func() T) (T, error) {
	result := make(chan T, 1)
	go func() {
		result <- callback()
	}()
	select {
	case value := <-result:
		return value, nil
	case <-ctx.Done():
		var zero T
		return zero, ctx.Err()
	}
}

func referenceIdentity(label string, value any) (uintptr, error) {
	reference := reflect.ValueOf(value)
	if !reference.IsValid() || reference.Kind() != reflect.Ptr || reference.IsNil() {
		return 0, fmt.Errorf("worker conformance: %s must be a non-nil concrete pointer", label)
	}
	return reference.Pointer(), nil
}

func validateFreshWorker(initial, restarted any) error {
	initialIdentity, err := referenceIdentity("initial worker", initial)
	if err != nil {
		return err
	}
	restartedIdentity, err := referenceIdentity("restarted worker", restarted)
	if err != nil {
		return err
	}
	if reflect.TypeOf(initial) != reflect.TypeOf(restarted) {
		return fmt.Errorf("worker conformance: restarted worker type changed from %T to %T", initial, restarted)
	}
	if restartedIdentity == initialIdentity {
		return errors.New("worker conformance: restart reused the initial worker instance")
	}
	return nil
}
