// Package builtin wires the built-in auth providers to their engines and
// registers a name-keyed factory that transports use to obtain an
// [engine.Session]. The factory replaces the former carrier layer: when
// the auth provider is "none" the caller supplies engine/URL/token
// directly; otherwise the named provider issues credentials and the
// matching engine is constructed.
package builtin

import (
	"context"
	"errors"
	"fmt"

	"github.com/openlibrecommunity/olcrtc/internal/auth"
	authJitsi "github.com/openlibrecommunity/olcrtc/internal/auth/jitsi"
	authTelemost "github.com/openlibrecommunity/olcrtc/internal/auth/telemost"
	authWBStream "github.com/openlibrecommunity/olcrtc/internal/auth/wbstream"
	"github.com/openlibrecommunity/olcrtc/internal/engine"
	_ "github.com/openlibrecommunity/olcrtc/internal/engine/goolom"  // register goolom engine via init
	_ "github.com/openlibrecommunity/olcrtc/internal/engine/jitsi"   // register jitsi engine via init
	_ "github.com/openlibrecommunity/olcrtc/internal/engine/livekit" // register livekit engine via init
)

// ErrCarrierNotFound is returned when an unregistered carrier name is requested.
var ErrCarrierNotFound = errors.New("carrier not found")

// ErrAuthFailed wraps an auth provider rejection. It pairs with the inner
// provider error returned from [Open].
var ErrAuthFailed = errors.New("carrier auth failed")

// Config holds the inputs to [Open]. The fields mirror the subset of
// transport.Config that engines consume.
type Config struct {
	RoomURL    string
	Name       string
	OnData     func([]byte)
	OnPeerData func(peerID string, data []byte)
	DNSServer  string
	ProxyAddr  string
	ProxyPort  int
	// Engine, URL, Token are honoured only for the "none" carrier (direct
	// engine access); other carriers derive them from their auth provider.
	Engine string
	URL    string
	Token  string
}

// Factory creates an engine session for a given carrier.
type Factory func(ctx context.Context, cfg Config) (engine.Session, error)

var registry = map[string]Factory{} //nolint:gochecknoglobals // package-level registry

// Register adds a carrier factory.
func Register(name string, f Factory) {
	registry[name] = f
}

// Open looks up the carrier factory and creates an engine session.
func Open(ctx context.Context, name string, cfg Config) (engine.Session, error) {
	f, ok := registry[name]
	if !ok {
		return nil, fmt.Errorf("%w: %q", ErrCarrierNotFound, name)
	}
	return f(ctx, cfg)
}

// Available reports all registered carrier names.
func Available() []string {
	names := make([]string, 0, len(registry))
	for name := range registry {
		names = append(names, name)
	}
	return names
}

// RegisterDefaults wires the built-in carriers: jitsi, telemost, wbstream
// and "none" (direct engine access).
func RegisterDefaults() {
	registerEngineAuth("wbstream", authWBStream.Provider{})
	registerEngineAuth("telemost", authTelemost.Provider{})
	registerEngineAuth("jitsi", authJitsi.Provider{})
	registerDirect("none")
}

// registerDirect registers a carrier that skips auth: the caller supplies
// engine/URL/token directly via [Config].
func registerDirect(name string) {
	Register(name, func(ctx context.Context, cfg Config) (engine.Session, error) {
		engineName := cfg.Engine
		if engineName == "" {
			engineName = "livekit"
		}
		sess, err := engine.New(ctx, engineName, engine.Config{
			URL:        cfg.URL,
			Token:      cfg.Token,
			Name:       cfg.Name,
			OnData:     cfg.OnData,
			OnPeerData: cfg.OnPeerData,
			DNSServer:  cfg.DNSServer,
			ProxyAddr:  cfg.ProxyAddr,
			ProxyPort:  cfg.ProxyPort,
		})
		if err != nil {
			return nil, fmt.Errorf("engine new: %w", err)
		}
		return sess, nil
	})
}

// registerEngineAuth registers a carrier that resolves credentials through an
// auth provider and connects via the engine the auth provider reports.
func registerEngineAuth(name string, provider auth.Provider) {
	Register(name, func(ctx context.Context, cfg Config) (engine.Session, error) {
		authCfg := auth.Config{
			RoomURL:   cfg.RoomURL,
			Name:      cfg.Name,
			DNSServer: cfg.DNSServer,
			ProxyAddr: cfg.ProxyAddr,
			ProxyPort: cfg.ProxyPort,
		}
		creds, err := provider.Issue(ctx, authCfg)
		if err != nil {
			return nil, fmt.Errorf("%w: %w", ErrAuthFailed, err)
		}
		sess, err := engine.New(ctx, provider.Engine(), engine.Config{
			URL:        creds.URL,
			Token:      creds.Token,
			Name:       cfg.Name,
			Extra:      creds.Extra,
			OnData:     cfg.OnData,
			OnPeerData: cfg.OnPeerData,
			DNSServer:  cfg.DNSServer,
			ProxyAddr:  cfg.ProxyAddr,
			ProxyPort:  cfg.ProxyPort,
			Refresh: func(ctx context.Context) (engine.Credentials, error) {
				fresh, err := provider.Issue(ctx, authCfg)
				if err != nil {
					return engine.Credentials{}, fmt.Errorf("auth refresh: %w", err)
				}
				return engine.Credentials{URL: fresh.URL, Token: fresh.Token, Extra: fresh.Extra}, nil
			},
		})
		if err != nil {
			return nil, fmt.Errorf("engine new: %w", err)
		}
		return sess, nil
	})
}
