package permissions

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/nexuscrm/kernel/pkg/ports"
)

func TestPermissionAuthoringConvergenceWorkerRecoversUnwokenPendingWork(t *testing.T) {
	store := &fakePermissionAuthoringConvergenceStore{work: []ports.PermissionAuthoringConvergenceWork{{
		Event: ports.PermissionAuthoringConvergenceEvent{
			CommandRowID: "row-1", CommandID: "command-1",
			CommandDigest:          "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
			CanonicalPermissionSet: "permission-set-1", AuthoringRevision: 8,
		},
	}}}
	handler := &fakePermissionAuthoringConvergenceHandler{}
	worker, err := NewPermissionAuthoringConvergenceWorker(store, handler, PermissionAuthoringConvergenceWorkerOptions{
		LeaseDuration: time.Minute, RetryDelay: time.Second, BatchSize: 10,
	})
	if err != nil {
		t.Fatal(err)
	}
	result, err := worker.RunBatch(t.Context(), "worker-batch-1")
	if err != nil || result.Claimed != 1 || result.Finalized != 1 || handler.calls != 1 ||
		store.completed != "row-1" || store.retried != "" {
		t.Fatalf("RunBatch() result=%+v err=%v handler=%d completed=%q retried=%q",
			result, err, handler.calls, store.completed, store.retried)
	}
}

func TestPermissionAuthoringConvergenceWorkerPersistsRetry(t *testing.T) {
	store := &fakePermissionAuthoringConvergenceStore{work: []ports.PermissionAuthoringConvergenceWork{{
		Event: ports.PermissionAuthoringConvergenceEvent{CommandRowID: "row-1"},
	}}}
	handlerErr := errors.New("projection unavailable")
	handler := &fakePermissionAuthoringConvergenceHandler{err: handlerErr}
	worker, err := NewPermissionAuthoringConvergenceWorker(store, handler, PermissionAuthoringConvergenceWorkerOptions{})
	if err != nil {
		t.Fatal(err)
	}
	result, err := worker.RunBatch(t.Context(), "worker-batch-2")
	if result.Claimed != 1 || result.Finalized != 0 || !errors.Is(err, handlerErr) ||
		store.retried != "row-1" || store.completed != "" {
		t.Fatalf("RunBatch() result=%+v err=%v completed=%q retried=%q", result, err, store.completed, store.retried)
	}
}

func TestPermissionAuthoringConvergenceWorkerCountsOnlyAcknowledgedWorkAsFinalized(t *testing.T) {
	handlerErr := errors.New("projection unavailable")
	ackErr := errors.New("acknowledgement unavailable")
	store := &fakePermissionAuthoringConvergenceStore{
		work: []ports.PermissionAuthoringConvergenceWork{
			{Event: ports.PermissionAuthoringConvergenceEvent{CommandRowID: "row-success"}},
			{Event: ports.PermissionAuthoringConvergenceEvent{CommandRowID: "row-retry"}},
			{Event: ports.PermissionAuthoringConvergenceEvent{CommandRowID: "row-ack-fail"}},
		},
		ackErrors: map[string]error{"row-ack-fail": ackErr},
	}
	handler := &fakePermissionAuthoringConvergenceHandler{
		errors: map[string]error{"row-retry": handlerErr},
	}
	worker, err := NewPermissionAuthoringConvergenceWorker(store, handler, PermissionAuthoringConvergenceWorkerOptions{})
	if err != nil {
		t.Fatal(err)
	}
	result, err := worker.RunBatch(t.Context(), "worker-batch-partial")
	if result.Claimed != 3 || result.Finalized != 1 || !errors.Is(err, handlerErr) || !errors.Is(err, ackErr) ||
		store.completed != "row-success" || store.retried != "row-retry" {
		t.Fatalf("RunBatch() result=%+v err=%v completed=%q retried=%q", result, err, store.completed, store.retried)
	}
}

func TestPermissionAuthoringReadbackConvergenceRequiresVisibleCommittedRevision(t *testing.T) {
	reader := &fakePermissionAuthoringRevisionReadback{revision: 9, found: true}
	handler, err := NewPermissionAuthoringReadbackConvergence(reader)
	if err != nil {
		t.Fatal(err)
	}
	event := ports.PermissionAuthoringConvergenceEvent{
		CanonicalPermissionSet: "permission-set-1",
		AuthoringRevision:      8,
	}
	if err := handler.ConvergePermissionAuthoring(t.Context(), event); err != nil {
		t.Fatalf("later visible revision did not prove convergence: %v", err)
	}
	reader.revision = 7
	if err := handler.ConvergePermissionAuthoring(t.Context(), event); err == nil {
		t.Fatal("read-back behind committed revision was acknowledged")
	}
	reader.found = false
	if err := handler.ConvergePermissionAuthoring(t.Context(), event); err == nil {
		t.Fatal("missing read-back target was acknowledged")
	}
}

type fakePermissionAuthoringRevisionReadback struct {
	revision uint64
	found    bool
	err      error
}

func (reader *fakePermissionAuthoringRevisionReadback) ReadPermissionSetAuthoringRevision(
	context.Context,
	string,
) (uint64, bool, error) {
	return reader.revision, reader.found, reader.err
}

type fakePermissionAuthoringConvergenceStore struct {
	work      []ports.PermissionAuthoringConvergenceWork
	completed string
	retried   string
	ackErrors map[string]error
}

func (*fakePermissionAuthoringConvergenceStore) EnqueuePermissionAuthoringConvergence(context.Context, ports.PermissionAuthoringConvergenceEvent) error {
	return nil
}

func (*fakePermissionAuthoringConvergenceStore) WakePermissionAuthoringConvergence() {}

func (store *fakePermissionAuthoringConvergenceStore) ClaimPermissionAuthoringConvergence(context.Context, string, time.Duration, int) ([]ports.PermissionAuthoringConvergenceWork, error) {
	return store.work, nil
}

func (store *fakePermissionAuthoringConvergenceStore) AcknowledgePermissionAuthoringConvergence(_ context.Context, rowID, _ string) error {
	if err := store.ackErrors[rowID]; err != nil {
		return err
	}
	store.completed = rowID
	return nil
}

func (store *fakePermissionAuthoringConvergenceStore) RetryPermissionAuthoringConvergence(_ context.Context, rowID, _, _ string, _ time.Duration) error {
	store.retried = rowID
	return nil
}

type fakePermissionAuthoringConvergenceHandler struct {
	calls  int
	err    error
	errors map[string]error
}

func (handler *fakePermissionAuthoringConvergenceHandler) ConvergePermissionAuthoring(_ context.Context, event ports.PermissionAuthoringConvergenceEvent) error {
	handler.calls++
	if err := handler.errors[event.CommandRowID]; err != nil {
		return err
	}
	return handler.err
}
