package events

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

	"github.com/nexuscrm/kernel/pkg/ports"
	apperrors "github.com/nexuscrm/kernel/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
}

// InternalRecordEventEnvelope is the serialized outbox transport shape for
// record events. Public subscribers only receive PublicPayload; InternalMetadata
// is reserved for worker-to-worker transport concerns such as trace continuity
// and AfterSave recursion state.
type InternalRecordEventEnvelope struct {
	PublicPayload    RecordEventPayload `json:"public_payload"`
	InternalMetadata map[string]string  `json:"internal_metadata,omitempty"`
}

// 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 {
			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
	var requestID string
	if p, ok := payload.(RecordEventPayload); ok && p.Metadata != nil {
		traceparent = p.Metadata[string(constants.ContextKeyTraceparent)]
		requestID = p.Metadata[string(constants.ContextKeyRequestID)]
	}

	// 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)
				}
			}()

			ctx, cancel := eventBusAsyncContext(traceparent, requestID)
			defer cancel()

			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 only async-safe observability values from the parent context.
	var traceparent string
	var requestID string
	if parentCtx != nil {
		traceparent, _ = parentCtx.Value(constants.ContextKeyTraceparent).(string)
		requestID, _ = parentCtx.Value(constants.ContextKeyRequestID).(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 := eventBusAsyncContext(traceparent, requestID)
			defer cancel()

			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)
	}
}

func eventBusAsyncContext(traceparent, requestID string) (context.Context, context.CancelFunc) {
	ctx := context.Background()
	if traceparent != "" {
		ctx = context.WithValue(ctx, constants.ContextKeyTraceparent, traceparent)
	}
	if requestID != "" {
		ctx = context.WithValue(ctx, constants.ContextKeyRequestID, requestID)
	}
	return context.WithTimeout(ctx, 60*time.Second)
}

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

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