package events

import (
	"context"
	"errors"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/nexuscrm/backend/internal/domain/ports"
	apperrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

// EventType is an alias to the domain type
type EventType = events.EventType

// RecordEventPayload represents payload for record events
type RecordEventPayload struct {
	ObjectAPIName string              `json:"object_api_name"`
	Record        models.SObject      `json:"record"`
	OldRecord     *models.SObject     `json:"old_record,omitempty"`
	CurrentUser   *models.UserSession `json:"current_user,omitempty"`
	Metadata      map[string]string   `json:"metadata,omitempty"` // Trace Context Envelope
}

// EventHandler is a function that handles an event.
// Using the type from ports to ensure interface compatibility.
type EventHandler = ports.EventHandler

// handlerEntry wraps a handler with a unique ID for reliable unsubscription
type handlerEntry struct {
	id      uint64
	handler EventHandler
}

// MetricsCallbacks contains optional callbacks for recording EventBus metrics.
// This allows decoupling from the middleware package to avoid import cycles.
type MetricsCallbacks struct {
	// OnEventDropped is called when an event is dropped due to queue saturation.
	// The parameter is the event type string.
	OnEventDropped func(eventType string)
	// OnQueueUtilizationChange is called when the queue utilization changes.
	// The parameter is the current number of events in the queue.
	OnQueueUtilizationChange func(current float64)
}

// EventBus manages publish-subscribe event system.
// It implements ports.EventPublisher interface.
type EventBus struct {
	handlers  map[EventType][]handlerEntry
	mu        sync.RWMutex
	semaphore chan struct{} // Semaphore to limit async concurrency
	nextID    uint64        // Atomic counter for unique handler IDs
	queueSize int           // Configurable queue size (default: 1000)
	metrics   MetricsCallbacks
}

// Ensure EventBus implements ports.EventPublisher at compile time
var _ ports.EventPublisher = (*EventBus)(nil)

// DefaultQueueSize is the maximum number of concurrent async events
const DefaultQueueSize = 1000

// NewEventBus creates a new EventBus instance with default queue size
func NewEventBus() *EventBus {
	return NewEventBusWithQueueSize(DefaultQueueSize)
}

// NewEventBusWithQueueSize creates a new EventBus with configurable queue size
func NewEventBusWithQueueSize(queueSize int) *EventBus {
	if queueSize <= 0 {
		queueSize = DefaultQueueSize
	}
	return &EventBus{
		handlers:  make(map[EventType][]handlerEntry),
		semaphore: make(chan struct{}, queueSize),
		queueSize: queueSize,
	}
}

// SetMetricsCallbacks sets optional callbacks for recording metrics.
// This should be called during initialization to enable metrics collection.
func (eb *EventBus) SetMetricsCallbacks(callbacks MetricsCallbacks) {
	eb.metrics = callbacks
}

// Subscribe registers a handler for a specific event type
// Returns an unsubscribe function with reliable ID-based removal
func (eb *EventBus) Subscribe(eventType EventType, handler EventHandler) func() {
	eb.mu.Lock()
	defer eb.mu.Unlock()

	if eb.handlers[eventType] == nil {
		eb.handlers[eventType] = make([]handlerEntry, 0)
	}

	// Assign unique ID atomically (under lock, so atomic not needed)
	eb.nextID++
	handlerID := eb.nextID

	eb.handlers[eventType] = append(eb.handlers[eventType], handlerEntry{
		id:      handlerID,
		handler: handler,
	})

	// Return unsubscribe function that uses ID for reliable removal
	return func() {
		eb.mu.Lock()
		defer eb.mu.Unlock()

		handlers := eb.handlers[eventType]
		for i, entry := range handlers {
			if entry.id == handlerID {
				eb.handlers[eventType] = append(handlers[:i], handlers[i+1:]...)
				break
			}
		}
	}
}

// Publish publishes an event to all registered handlers
func (eb *EventBus) Publish(ctx context.Context, eventType EventType, payload interface{}) error {
	eb.mu.RLock()
	handlers := eb.handlers[eventType]
	eb.mu.RUnlock()

	if len(handlers) == 0 {
		return nil
	}

	// Execute handlers in sequence with Panic Recovery
	for _, entry := range handlers {
		err := func() (valErr error) {
			defer func() {
				if r := recover(); r != nil {
					valErr = apperrors.NewInternalError("panic in event handler", nil)
					log.Printf("🔥 CRITICAL: EventBus Panic recovered for %s: %v", eventType, r)
				}
			}()
			return entry.handler(ctx, payload)
		}()

		if err != nil {
			var appErr apperrors.AppError
			if errors.As(err, &appErr) {
				return appErr
			}
			return apperrors.NewInternalError(fmt.Sprintf("EventBus handler error for %s", eventType), err)
		}
	}

	return nil
}

// recordQueueUtilization calls the metrics callback if set
func (eb *EventBus) recordQueueUtilization() {
	if eb.metrics.OnQueueUtilizationChange != nil {
		eb.metrics.OnQueueUtilizationChange(float64(len(eb.semaphore)))
	}
}

// recordEventDropped calls the metrics callback if set
func (eb *EventBus) recordEventDropped(eventType EventType) {
	if eb.metrics.OnEventDropped != nil {
		eb.metrics.OnEventDropped(string(eventType))
	}
}

// PublishAsync publishes an event asynchronously with trace context propagation.
// It extracts traceparent from the payload and injects it into the async execution context
// to maintain the "Golden Thread" across async boundaries.
func (eb *EventBus) PublishAsync(eventType EventType, payload interface{}) {
	// Extract trace context from payload if it's a RecordEventPayload
	var traceparent string
	if p, ok := payload.(RecordEventPayload); ok && p.Metadata != nil {
		traceparent = p.Metadata[string(constants.ContextKeyTraceparent)]
	}

	// Non-blocking try to acquire semaphore
	select {
	case eb.semaphore <- struct{}{}:
		// Update queue utilization metric
		eb.recordQueueUtilization()
		go func() {
			defer func() {
				<-eb.semaphore
				// Update queue utilization after releasing
				eb.recordQueueUtilization()
			}()

			// Panic Recovery for the goroutine itself
			defer func() {
				if r := recover(); r != nil {
					log.Printf("🔥 CRITICAL: EventBus Async Panic: %v", r)
				}
			}()

			// Use background context for async events as they are decoupled from the request/tx
			// But enforce a timeout to prevent goroutine leaks if handlers block
			ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
			defer cancel()

			// Propagate trace context using traceparent only
			if traceparent != "" {
				ctx = context.WithValue(ctx, constants.ContextKeyTraceparent, traceparent)
			}

			if err := eb.Publish(ctx, eventType, payload); err != nil {
				log.Printf("EventBus async publish error: %v", err)
			}
		}()
	default:
		// Queue full - record metric and log warning
		eb.recordEventDropped(eventType)
		log.Printf("⚠️ EventBus dropped event %s due to high load (%d concurrent tasks)", eventType, eb.queueSize)
	}
}

// PublishAsyncWithContext publishes an event asynchronously with explicit parent context.
// This is the preferred method when you have access to a request context with trace information.
func (eb *EventBus) PublishAsyncWithContext(parentCtx context.Context, eventType EventType, payload interface{}) {
	// Extract trace context from parent using traceparent only
	traceparent, _ := parentCtx.Value(constants.ContextKeyTraceparent).(string)

	select {
	case eb.semaphore <- struct{}{}:
		// Update queue utilization metric
		eb.recordQueueUtilization()
		go func() {
			defer func() {
				<-eb.semaphore
				// Update queue utilization after releasing
				eb.recordQueueUtilization()
			}()

			defer func() {
				if r := recover(); r != nil {
					log.Printf("🔥 CRITICAL: EventBus Async Panic: %v", r)
				}
			}()

			ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
			defer cancel()

			// Propagate trace context using traceparent only
			if traceparent != "" {
				ctx = context.WithValue(ctx, constants.ContextKeyTraceparent, traceparent)
			}

			if err := eb.Publish(ctx, eventType, payload); err != nil {
				log.Printf("EventBus async publish error: %v", err)
			}
		}()
	default:
		// Queue full - record metric and log warning
		eb.recordEventDropped(eventType)
		log.Printf("⚠️ EventBus dropped event %s due to high load (%d concurrent tasks)", eventType, eb.queueSize)
	}
}

// Clear removes all handlers (useful for testing)
func (eb *EventBus) Clear() {
	eb.mu.Lock()
	defer eb.mu.Unlock()

	eb.handlers = make(map[EventType][]handlerEntry)
}
