package processing

import (
	"context"
	"errors"
	"testing"

	"git.mleku.dev/mleku/nostr/encoders/event"
)

// mockDatabase is a mock implementation of Database for testing.
type mockDatabase struct {
	saveErr    error
	saveExists bool
	checkErr   error
}

func (m *mockDatabase) SaveEvent(ctx context.Context, ev *event.E) (exists bool, err error) {
	return m.saveExists, m.saveErr
}

func (m *mockDatabase) CheckForDeleted(ev *event.E, adminOwners [][]byte) error {
	return m.checkErr
}

// mockPublisher is a mock implementation of Publisher for testing.
type mockPublisher struct {
	deliveredEvents []*event.E
}

func (m *mockPublisher) Deliver(ev *event.E) {
	m.deliveredEvents = append(m.deliveredEvents, ev)
}

// mockRateLimiter is a mock implementation of RateLimiter for testing.
type mockRateLimiter struct {
	enabled    bool
	waitCalled bool
}

func (m *mockRateLimiter) IsEnabled() bool {
	return m.enabled
}

func (m *mockRateLimiter) Wait(ctx context.Context, opType int) error {
	m.waitCalled = true
	return nil
}

// mockSyncManager is a mock implementation of SyncManager for testing.
type mockSyncManager struct {
	updateCalled bool
}

func (m *mockSyncManager) UpdateSerial() {
	m.updateCalled = true
}

// mockACLRegistry is a mock implementation of ACLRegistry for testing.
type mockACLRegistry struct {
	active         string
	configureCalls int
}

func (m *mockACLRegistry) Configure(cfg ...any) error {
	m.configureCalls++
	return nil
}

func (m *mockACLRegistry) Active() string {
	return m.active
}

func TestNew(t *testing.T) {
	db := &mockDatabase{}
	pub := &mockPublisher{}

	s := New(nil, db, pub)
	if s == nil {
		t.Fatal("New() returned nil")
	}
	if s.cfg == nil {
		t.Fatal("cfg should be set to default")
	}
	if s.db != db {
		t.Fatal("db not set correctly")
	}
	if s.publisher != pub {
		t.Fatal("publisher not set correctly")
	}
}

func TestDefaultConfig(t *testing.T) {
	cfg := DefaultConfig()
	if cfg.WriteTimeout != 30*1e9 {
		t.Errorf("expected WriteTimeout=30s, got %v", cfg.WriteTimeout)
	}
}

func TestResultConstructors(t *testing.T) {
	// OK
	r := OK()
	if !r.Saved || r.Error != nil || r.Blocked {
		t.Error("OK() should return Saved=true")
	}

	// Blocked
	r = Blocked("test blocked")
	if r.Saved || !r.Blocked || r.BlockMsg != "test blocked" {
		t.Error("Blocked() should return Blocked=true with message")
	}

	// Failed
	err := errors.New("test error")
	r = Failed(err)
	if r.Saved || r.Error != err {
		t.Error("Failed() should return Error set")
	}
}

func TestProcess_Success(t *testing.T) {
	db := &mockDatabase{}
	pub := &mockPublisher{}

	s := New(nil, db, pub)

	ev := event.New()
	ev.Kind = 1
	ev.Pubkey = make([]byte, 32)

	result := s.Process(context.Background(), ev)
	if !result.Saved {
		t.Errorf("should save successfully: %v", result.Error)
	}
}

func TestProcess_DatabaseError(t *testing.T) {
	testErr := errors.New("db error")
	db := &mockDatabase{saveErr: testErr}
	pub := &mockPublisher{}

	s := New(nil, db, pub)

	ev := event.New()
	ev.Kind = 1
	ev.Pubkey = make([]byte, 32)

	result := s.Process(context.Background(), ev)
	if result.Saved {
		t.Error("should not save on error")
	}
	if result.Error != testErr {
		t.Error("should return the database error")
	}
}

func TestProcess_BlockedError(t *testing.T) {
	db := &mockDatabase{saveErr: errors.New("blocked: event already deleted")}
	pub := &mockPublisher{}

	s := New(nil, db, pub)

	ev := event.New()
	ev.Kind = 1
	ev.Pubkey = make([]byte, 32)

	result := s.Process(context.Background(), ev)
	if result.Saved {
		t.Error("should not save blocked events")
	}
	if !result.Blocked {
		t.Error("should mark as blocked")
	}
	if result.BlockMsg != "event already deleted" {
		t.Errorf("expected block message, got: %s", result.BlockMsg)
	}
}

func TestProcess_WithRateLimiter(t *testing.T) {
	db := &mockDatabase{}
	pub := &mockPublisher{}
	rl := &mockRateLimiter{enabled: true}

	s := New(nil, db, pub)
	s.SetRateLimiter(rl)

	ev := event.New()
	ev.Kind = 1
	ev.Pubkey = make([]byte, 32)

	s.Process(context.Background(), ev)

	if !rl.waitCalled {
		t.Error("rate limiter Wait should be called")
	}
}

func TestProcess_WithSyncManager(t *testing.T) {
	db := &mockDatabase{}
	pub := &mockPublisher{}
	sm := &mockSyncManager{}

	s := New(nil, db, pub)
	s.SetSyncManager(sm)

	ev := event.New()
	ev.Kind = 1
	ev.Pubkey = make([]byte, 32)

	s.Process(context.Background(), ev)

	if !sm.updateCalled {
		t.Error("sync manager UpdateSerial should be called")
	}
}

func TestProcess_AdminFollowListTriggersACLReconfigure(t *testing.T) {
	db := &mockDatabase{}
	pub := &mockPublisher{}
	acl := &mockACLRegistry{active: "follows"}

	adminPubkey := make([]byte, 32)
	for i := range adminPubkey {
		adminPubkey[i] = byte(i)
	}

	cfg := &Config{
		Admins: [][]byte{adminPubkey},
	}

	s := New(cfg, db, pub)
	s.SetACLRegistry(acl)

	ev := event.New()
	ev.Kind = 3 // FollowList
	ev.Pubkey = adminPubkey

	s.Process(context.Background(), ev)

	// Give goroutine time to run
	// In production this would be tested differently
	// For now just verify the path is exercised
}

func TestSetters(t *testing.T) {
	db := &mockDatabase{}
	pub := &mockPublisher{}
	s := New(nil, db, pub)

	rl := &mockRateLimiter{}
	s.SetRateLimiter(rl)
	if s.rateLimiter != rl {
		t.Error("SetRateLimiter should set rateLimiter")
	}

	sm := &mockSyncManager{}
	s.SetSyncManager(sm)
	if s.syncManager != sm {
		t.Error("SetSyncManager should set syncManager")
	}

	acl := &mockACLRegistry{}
	s.SetACLRegistry(acl)
	if s.aclRegistry != acl {
		t.Error("SetACLRegistry should set aclRegistry")
	}
}

func TestIsAdminEvent(t *testing.T) {
	adminPubkey := make([]byte, 32)
	for i := range adminPubkey {
		adminPubkey[i] = byte(i)
	}

	ownerPubkey := make([]byte, 32)
	for i := range ownerPubkey {
		ownerPubkey[i] = byte(i + 50)
	}

	cfg := &Config{
		Admins: [][]byte{adminPubkey},
		Owners: [][]byte{ownerPubkey},
	}

	s := New(cfg, &mockDatabase{}, &mockPublisher{})

	// Admin event
	ev := event.New()
	ev.Pubkey = adminPubkey
	if !s.isAdminEvent(ev) {
		t.Error("should recognize admin event")
	}

	// Owner event
	ev.Pubkey = ownerPubkey
	if !s.isAdminEvent(ev) {
		t.Error("should recognize owner event")
	}

	// Regular event
	ev.Pubkey = make([]byte, 32)
	for i := range ev.Pubkey {
		ev.Pubkey[i] = byte(i + 100)
	}
	if s.isAdminEvent(ev) {
		t.Error("should not recognize regular event as admin")
	}
}

func TestFastEqual(t *testing.T) {
	a := []byte{1, 2, 3, 4}
	b := []byte{1, 2, 3, 4}
	c := []byte{1, 2, 3, 5}
	d := []byte{1, 2, 3}

	if !fastEqual(a, b) {
		t.Error("equal slices should return true")
	}
	if fastEqual(a, c) {
		t.Error("different values should return false")
	}
	if fastEqual(a, d) {
		t.Error("different lengths should return false")
	}
}
