package push

import (
	"context"
	"fmt"
	"testing"
)

// TestProcessorCreation tests processor creation and initialization
func TestProcessorCreation(t *testing.T) {
	t.Run("NewProcessor", func(t *testing.T) {
		processor := NewProcessor()
		if processor == nil {
			t.Fatal("NewProcessor should not return nil")
		}
		if processor.registry == nil {
			t.Error("Processor should have a registry")
		}
	})

	t.Run("NewVoidProcessor", func(t *testing.T) {
		voidProcessor := NewVoidProcessor()
		if voidProcessor == nil {
			t.Fatal("NewVoidProcessor should not return nil")
		}
	})
}

// TestProcessorHandlerManagement tests handler registration and retrieval
func TestProcessorHandlerManagement(t *testing.T) {
	processor := NewProcessor()
	handler := &UnitTestHandler{name: "test-handler"}

	t.Run("RegisterHandler", func(t *testing.T) {
		err := processor.RegisterHandler("TEST", handler, false)
		if err != nil {
			t.Errorf("RegisterHandler should not error: %v", err)
		}

		// Verify handler is registered
		retrievedHandler := processor.GetHandler("TEST")
		if retrievedHandler != handler {
			t.Error("GetHandler should return the registered handler")
		}
	})

	t.Run("RegisterProtectedHandler", func(t *testing.T) {
		protectedHandler := &UnitTestHandler{name: "protected-handler"}
		err := processor.RegisterHandler("PROTECTED", protectedHandler, true)
		if err != nil {
			t.Errorf("RegisterHandler should not error for protected handler: %v", err)
		}

		// Verify handler is registered
		retrievedHandler := processor.GetHandler("PROTECTED")
		if retrievedHandler != protectedHandler {
			t.Error("GetHandler should return the protected handler")
		}
	})

	t.Run("GetNonExistentHandler", func(t *testing.T) {
		handler := processor.GetHandler("NONEXISTENT")
		if handler != nil {
			t.Error("GetHandler should return nil for non-existent handler")
		}
	})

	t.Run("UnregisterHandler", func(t *testing.T) {
		err := processor.UnregisterHandler("TEST")
		if err != nil {
			t.Errorf("UnregisterHandler should not error: %v", err)
		}

		// Verify handler is removed
		retrievedHandler := processor.GetHandler("TEST")
		if retrievedHandler != nil {
			t.Error("GetHandler should return nil after unregistering")
		}
	})

	t.Run("UnregisterProtectedHandler", func(t *testing.T) {
		err := processor.UnregisterHandler("PROTECTED")
		if err == nil {
			t.Error("UnregisterHandler should error for protected handler")
		}

		// Verify handler is still there
		retrievedHandler := processor.GetHandler("PROTECTED")
		if retrievedHandler == nil {
			t.Error("Protected handler should not be removed")
		}
	})
}

// TestVoidProcessorBehavior tests void processor behavior
func TestVoidProcessorBehavior(t *testing.T) {
	voidProcessor := NewVoidProcessor()
	handler := &UnitTestHandler{name: "test-handler"}

	t.Run("GetHandler", func(t *testing.T) {
		retrievedHandler := voidProcessor.GetHandler("ANY")
		if retrievedHandler != nil {
			t.Error("VoidProcessor GetHandler should always return nil")
		}
	})

	t.Run("RegisterHandler", func(t *testing.T) {
		err := voidProcessor.RegisterHandler("TEST", handler, false)
		if err == nil {
			t.Error("VoidProcessor RegisterHandler should return error")
		}

		// Check error type
		if !IsVoidProcessorError(err) {
			t.Error("Error should be a VoidProcessorError")
		}
	})

	t.Run("UnregisterHandler", func(t *testing.T) {
		err := voidProcessor.UnregisterHandler("TEST")
		if err == nil {
			t.Error("VoidProcessor UnregisterHandler should return error")
		}

		// Check error type
		if !IsVoidProcessorError(err) {
			t.Error("Error should be a VoidProcessorError")
		}
	})
}

// TestProcessPendingNotificationsNilReader tests handling of nil reader
func TestProcessPendingNotificationsNilReader(t *testing.T) {
	t.Run("ProcessorWithNilReader", func(t *testing.T) {
		processor := NewProcessor()
		ctx := context.Background()
		handlerCtx := NotificationHandlerContext{}

		err := processor.ProcessPendingNotifications(ctx, handlerCtx, nil)
		if err != nil {
			t.Errorf("ProcessPendingNotifications should not error with nil reader: %v", err)
		}
	})

	t.Run("VoidProcessorWithNilReader", func(t *testing.T) {
		voidProcessor := NewVoidProcessor()
		ctx := context.Background()
		handlerCtx := NotificationHandlerContext{}

		err := voidProcessor.ProcessPendingNotifications(ctx, handlerCtx, nil)
		if err != nil {
			t.Errorf("VoidProcessor ProcessPendingNotifications should not error with nil reader: %v", err)
		}
	})
}

// TestWillHandleNotificationInClient tests the notification filtering logic
func TestWillHandleNotificationInClient(t *testing.T) {
	testCases := []struct {
		name           string
		notificationType string
		shouldHandle   bool
	}{
		// Pub/Sub notifications (should be handled in client)
		{"message", "message", true},
		{"pmessage", "pmessage", true},
		{"subscribe", "subscribe", true},
		{"unsubscribe", "unsubscribe", true},
		{"psubscribe", "psubscribe", true},
		{"punsubscribe", "punsubscribe", true},
		{"smessage", "smessage", true},
		{"ssubscribe", "ssubscribe", true},
		{"sunsubscribe", "sunsubscribe", true},

		// Push notifications (should be handled by processor)
		{"MOVING", "MOVING", false},
		{"MIGRATING", "MIGRATING", false},
		{"MIGRATED", "MIGRATED", false},
		{"FAILING_OVER", "FAILING_OVER", false},
		{"FAILED_OVER", "FAILED_OVER", false},
		{"custom", "custom", false},
		{"unknown", "unknown", false},
		{"empty", "", false},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := willHandleNotificationInClient(tc.notificationType)
			if result != tc.shouldHandle {
				t.Errorf("willHandleNotificationInClient(%q) = %v, want %v", tc.notificationType, result, tc.shouldHandle)
			}
		})
	}
}

// TestProcessorErrorHandlingUnit tests error handling scenarios
func TestProcessorErrorHandlingUnit(t *testing.T) {
	processor := NewProcessor()

	t.Run("RegisterNilHandler", func(t *testing.T) {
		err := processor.RegisterHandler("TEST", nil, false)
		if err == nil {
			t.Error("RegisterHandler should error with nil handler")
		}

		// Check error type
		if !IsHandlerNilError(err) {
			t.Error("Error should be a HandlerNilError")
		}
	})

	t.Run("RegisterDuplicateHandler", func(t *testing.T) {
		handler1 := &UnitTestHandler{name: "handler1"}
		handler2 := &UnitTestHandler{name: "handler2"}

		// Register first handler
		err := processor.RegisterHandler("DUPLICATE", handler1, false)
		if err != nil {
			t.Errorf("First RegisterHandler should not error: %v", err)
		}

		// Try to register second handler with same name
		err = processor.RegisterHandler("DUPLICATE", handler2, false)
		if err == nil {
			t.Error("RegisterHandler should error when registering duplicate handler")
		}

		// Verify original handler is still there
		retrievedHandler := processor.GetHandler("DUPLICATE")
		if retrievedHandler != handler1 {
			t.Error("Original handler should remain after failed duplicate registration")
		}
	})

	t.Run("UnregisterNonExistentHandler", func(t *testing.T) {
		err := processor.UnregisterHandler("NONEXISTENT")
		if err != nil {
			t.Errorf("UnregisterHandler should not error for non-existent handler: %v", err)
		}
	})
}

// TestProcessorConcurrentAccess tests concurrent access to processor
func TestProcessorConcurrentAccess(t *testing.T) {
	processor := NewProcessor()
	
	t.Run("ConcurrentRegisterAndGet", func(t *testing.T) {
		done := make(chan bool, 2)

		// Goroutine 1: Register handlers
		go func() {
			defer func() { done <- true }()
			for i := 0; i < 100; i++ {
				handler := &UnitTestHandler{name: "concurrent-handler"}
				processor.RegisterHandler("CONCURRENT", handler, false)
				processor.UnregisterHandler("CONCURRENT")
			}
		}()

		// Goroutine 2: Get handlers
		go func() {
			defer func() { done <- true }()
			for i := 0; i < 100; i++ {
				processor.GetHandler("CONCURRENT")
			}
		}()

		// Wait for both goroutines to complete
		<-done
		<-done
	})
}

// TestProcessorInterfaceCompliance tests interface compliance
func TestProcessorInterfaceCompliance(t *testing.T) {
	t.Run("ProcessorImplementsInterface", func(t *testing.T) {
		var _ NotificationProcessor = (*Processor)(nil)
	})

	t.Run("VoidProcessorImplementsInterface", func(t *testing.T) {
		var _ NotificationProcessor = (*VoidProcessor)(nil)
	})
}

// UnitTestHandler is a test implementation of NotificationHandler
type UnitTestHandler struct {
	name           string
	lastNotification []interface{}
	errorToReturn  error
	callCount      int
}

func (h *UnitTestHandler) HandlePushNotification(ctx context.Context, handlerCtx NotificationHandlerContext, notification []interface{}) error {
	h.callCount++
	h.lastNotification = notification
	return h.errorToReturn
}

// Helper methods for UnitTestHandler
func (h *UnitTestHandler) GetCallCount() int {
	return h.callCount
}

func (h *UnitTestHandler) GetLastNotification() []interface{} {
	return h.lastNotification
}

func (h *UnitTestHandler) SetErrorToReturn(err error) {
	h.errorToReturn = err
}

func (h *UnitTestHandler) Reset() {
	h.callCount = 0
	h.lastNotification = nil
	h.errorToReturn = nil
}

// TestErrorWrapping tests that error checking functions work with wrapped errors
func TestErrorWrapping(t *testing.T) {
	t.Run("IsHandlerExistsError with wrapped error", func(t *testing.T) {
		// Create a HandlerError
		handlerErr := ErrHandlerExists("test-notification")

		// Wrap it
		wrappedErr := fmt.Errorf("operation failed: %w", handlerErr)
		doubleWrappedErr := fmt.Errorf("context: %w", wrappedErr)

		// Should still be detected through wrapping
		if !IsHandlerExistsError(doubleWrappedErr) {
			t.Errorf("IsHandlerExistsError should detect wrapped error")
		}

		// Verify it doesn't match other error types
		if IsProtectedHandlerError(doubleWrappedErr) {
			t.Errorf("IsProtectedHandlerError should not match handler exists error")
		}
	})

	t.Run("IsProtectedHandlerError with wrapped error", func(t *testing.T) {
		// Create a protected handler error
		protectedErr := ErrProtectedHandler("protected-notification")

		// Wrap it
		wrappedErr := fmt.Errorf("unregister failed: %w", protectedErr)

		// Should still be detected through wrapping
		if !IsProtectedHandlerError(wrappedErr) {
			t.Errorf("IsProtectedHandlerError should detect wrapped error")
		}

		// Verify it doesn't match other error types
		if IsHandlerExistsError(wrappedErr) {
			t.Errorf("IsHandlerExistsError should not match protected handler error")
		}
	})

	t.Run("IsVoidProcessorError with wrapped error", func(t *testing.T) {
		// Create a void processor error
		voidErr := ErrVoidProcessorRegister("test-notification")

		// Wrap it multiple times
		wrappedErr := fmt.Errorf("register failed: %w", voidErr)
		doubleWrappedErr := fmt.Errorf("processor error: %w", wrappedErr)

		// Should still be detected through wrapping
		if !IsVoidProcessorError(doubleWrappedErr) {
			t.Errorf("IsVoidProcessorError should detect wrapped error")
		}
	})

	t.Run("IsHandlerNilError with wrapped error", func(t *testing.T) {
		// Wrap the nil handler error
		wrappedErr := fmt.Errorf("validation failed: %w", ErrHandlerNil)

		// Should still be detected through wrapping
		if !IsHandlerNilError(wrappedErr) {
			t.Errorf("IsHandlerNilError should detect wrapped error")
		}
	})

	t.Run("Error functions return false for non-matching errors", func(t *testing.T) {
		// Create a different error
		otherErr := fmt.Errorf("some other error")

		if IsHandlerExistsError(otherErr) {
			t.Errorf("IsHandlerExistsError should return false for non-matching error")
		}
		if IsProtectedHandlerError(otherErr) {
			t.Errorf("IsProtectedHandlerError should return false for non-matching error")
		}
		if IsVoidProcessorError(otherErr) {
			t.Errorf("IsVoidProcessorError should return false for non-matching error")
		}
		if IsHandlerNilError(otherErr) {
			t.Errorf("IsHandlerNilError should return false for non-matching error")
		}
	})
}
