package ws

import (
	"context"
	"fmt"
	"log/slog"

	"github.com/owncord/server/stackutil"
)

// handlerV2Entry pairs a V2 handler with its domain-specific dependency struct.
type handlerV2Entry struct {
	handler HandlerV2
	deps    any // concrete deps struct for this handler's domain
}

// HandlerRegistry maps message type strings to their typed V2 handlers.
// It is not safe for concurrent use after initialization; all RegisterV2
// calls must happen before any DispatchV2 calls.
type HandlerRegistry struct {
	handlersV2 map[string]handlerV2Entry
}

// NewHandlerRegistry creates an empty handler registry.
func NewHandlerRegistry() *HandlerRegistry {
	return &HandlerRegistry{
		handlersV2: make(map[string]handlerV2Entry),
	}
}

// RegisterV2 registers a V2 handler for the given command type.
// PANICS if cmdType is already registered (duplicate guard).
func (r *HandlerRegistry) RegisterV2(cmdType string, handler HandlerV2, deps any) {
	if _, exists := r.handlersV2[cmdType]; exists {
		panic(fmt.Sprintf("RegisterV2: cmdType %q already registered", cmdType))
	}
	r.handlersV2[cmdType] = handlerV2Entry{handler: handler, deps: deps}
}

// DispatchV2 looks up a V2 handler and calls it.
// Returns (result, true) if found, (Result{}, false) if not.
// Recovers from panics (e.g. bad type assertions on cmd or deps) to prevent
// a single malformed message from crashing the entire server.
func (r *HandlerRegistry) DispatchV2(ctx context.Context, cmd Command, info ClientInfo) (result Result, ok bool) {
	entry, found := r.handlersV2[cmd.Type()]
	if !found {
		return Result{}, false
	}
	defer func() {
		if rec := recover(); rec != nil {
			// stackutil.Capture omits argument values, which for E2EE
			// handlers can include encrypted key material.
			slog.Error("DispatchV2 panic recovered",
				"type", cmd.Type(),
				"user_id", info.UserID,
				"panic", rec,
				"stack", stackutil.Capture(),
			)
			result = Result{Error: ClientError{Code: ErrCodeInternal, Message: "internal error"}}
			ok = true
		}
	}()
	return entry.handler(ctx, cmd, info, entry.deps), true
}

// RegisteredV2Types returns all V2-registered message types (unordered).
// Intended for testing and diagnostics.
func (r *HandlerRegistry) RegisteredV2Types() []string {
	types := make([]string, 0, len(r.handlersV2))
	for t := range r.handlersV2 {
		types = append(types, t)
	}
	return types
}
