package protosocket

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"sync"
)

type HandlerFunc func(context.Context, Envelope) Envelope

type Dispatcher struct {
	mu       sync.RWMutex
	handlers map[string]HandlerFunc
}

func NewDispatcher() *Dispatcher {
	return &Dispatcher{handlers: make(map[string]HandlerFunc)}
}

func (d *Dispatcher) Register(action string, handler HandlerFunc) {
	d.mu.Lock()
	defer d.mu.Unlock()
	d.handlers[action] = handler
}

func (d *Dispatcher) Dispatch(ctx context.Context, env Envelope) Envelope {
	d.mu.RLock()
	handler, ok := d.handlers[env.Action]
	d.mu.RUnlock()
	if !ok {
		return ErrorResponse(env, "UNSUPPORTED_ACTION", fmt.Sprintf("unsupported action: %s", env.Action), false)
	}
	return handler(ctx, env)
}

func SuccessResponse(req Envelope, payload map[string]any) Envelope {
	return Envelope{
		ProtocolVersion: ProtocolVersion,
		ID:              generateID(),
		CorrelationID:   req.ID,
		Type:            "response",
		Channel:         req.Channel,
		Action:          req.Action,
		Payload:         payload,
	}
}

func ErrorResponse(req Envelope, code, message string, retryable bool) Envelope {
	return Envelope{
		ProtocolVersion: ProtocolVersion,
		ID:              generateID(),
		CorrelationID:   req.ID,
		Type:            "error",
		Channel:         req.Channel,
		Action:          req.Action,
		Error: &EnvelopeError{
			Code:      code,
			Message:   message,
			Retryable: retryable,
		},
	}
}

func NewEventEnvelope(action string, payload map[string]any) Envelope {
	return Envelope{
		ProtocolVersion: ProtocolVersion,
		ID:              generateID(),
		Type:            "event",
		Channel:         "event",
		Action:          action,
		Payload:         payload,
	}
}

func generateID() string {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return "id-fallback"
	}
	return hex.EncodeToString(b)
}
