package handlers

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

	"github.com/openova-io/openova/core/services/shared/events"
)

// fakeMembersDeleter records the arguments passed to DeleteMembersByTenant so
// the consumer's behavior can be asserted without a live FerretDB.
type fakeMembersDeleter struct {
	calls []string
	n     int64
	err   error
}

func (f *fakeMembersDeleter) DeleteMembersByTenant(_ context.Context, tenantID string) (int64, error) {
	f.calls = append(f.calls, tenantID)
	if f.err != nil {
		return 0, f.err
	}
	return f.n, nil
}

func mkMembersTenantDeletedEvent(tenantID, slug string) *events.Event {
	payload, _ := json.Marshal(map[string]string{"id": tenantID, "slug": slug})
	return &events.Event{
		ID:       "evt-mem-td",
		Type:     "tenant.deleted",
		TenantID: tenantID,
		Data:     payload,
	}
}

// TestMembersHandleTenantDeleted_DeletesMembers is the primary #96 regression
// guard. A valid tenant.deleted event must trigger exactly one purge of member
// rows keyed by the tenant ID in the payload.
func TestMembersHandleTenantDeleted_DeletesMembers(t *testing.T) {
	fake := &fakeMembersDeleter{n: 5}
	c := &MembersCleanupConsumer{deleter: fake}

	if err := c.handleTenantDeleted(context.Background(), mkMembersTenantDeletedEvent("tenant-7", "acme")); err != nil {
		t.Fatalf("handleTenantDeleted: %v", err)
	}
	if got := fake.calls; len(got) != 1 || got[0] != "tenant-7" {
		t.Fatalf("expected one delete for tenant-7, got %v", got)
	}
}

// TestMembersHandleTenantDeleted_FallsBackToEnvelopeTenantID: older or partial
// publishers may emit an empty inner body; the envelope TenantID still lets
// the cascade succeed.
func TestMembersHandleTenantDeleted_FallsBackToEnvelopeTenantID(t *testing.T) {
	fake := &fakeMembersDeleter{}
	c := &MembersCleanupConsumer{deleter: fake}
	evt := &events.Event{
		ID:       "evt",
		Type:     "tenant.deleted",
		TenantID: "tenant-envelope",
		Data:     json.RawMessage(`{}`),
	}
	if err := c.handleTenantDeleted(context.Background(), evt); err != nil {
		t.Fatalf("handleTenantDeleted: %v", err)
	}
	if got := fake.calls; len(got) != 1 || got[0] != "tenant-envelope" {
		t.Fatalf("want fallback to envelope id, got %v", got)
	}
}

// TestMembersHandleTenantDeleted_EmptyTenantIDSkipsSilently: no id anywhere
// means there is nothing to cascade. Must not touch the store and must not
// error (so the partition offset advances past the malformed event).
func TestMembersHandleTenantDeleted_EmptyTenantIDSkipsSilently(t *testing.T) {
	fake := &fakeMembersDeleter{}
	c := &MembersCleanupConsumer{deleter: fake}
	evt := &events.Event{
		ID:   "evt",
		Type: "tenant.deleted",
		Data: json.RawMessage(`{}`),
	}
	if err := c.handleTenantDeleted(context.Background(), evt); err != nil {
		t.Fatalf("handleTenantDeleted: %v", err)
	}
	if len(fake.calls) != 0 {
		t.Fatalf("store must not be called when no tenant id is available, got %v", fake.calls)
	}
}

// TestMembersHandleTenantDeleted_MalformedPayloadCommitsPastPoisonPill ensures
// a non-object payload doesn't wedge the partition — log + nil error so the
// offset advances.
func TestMembersHandleTenantDeleted_MalformedPayloadCommitsPastPoisonPill(t *testing.T) {
	fake := &fakeMembersDeleter{}
	c := &MembersCleanupConsumer{deleter: fake}
	evt := &events.Event{
		ID:       "evt",
		Type:     "tenant.deleted",
		TenantID: "tenant-x",
		Data:     json.RawMessage(`"not an object"`),
	}
	if err := c.handleTenantDeleted(context.Background(), evt); err != nil {
		t.Fatalf("handleTenantDeleted: %v", err)
	}
	if len(fake.calls) != 0 {
		t.Fatal("store must not be called when payload is malformed")
	}
}

// TestMembersHandleTenantDeleted_StoreErrorPropagates: a real DB failure must
// surface so the consumer does NOT commit and the event is redelivered.
func TestMembersHandleTenantDeleted_StoreErrorPropagates(t *testing.T) {
	fake := &fakeMembersDeleter{err: fmt.Errorf("ferretdb: connection refused")}
	c := &MembersCleanupConsumer{deleter: fake}
	if err := c.handleTenantDeleted(context.Background(), mkMembersTenantDeletedEvent("tenant-7", "acme")); err == nil {
		t.Fatal("expected error to propagate so broker redelivers, got nil")
	}
}
