package webrtc

import (
	"fmt"
	"sync"

	"github.com/datarhei/core/v16/config"
	corewebrtc "github.com/datarhei/core/v16/core/webrtc"
	"github.com/datarhei/core/v16/log"
	"github.com/datarhei/core/v16/restream"
)

// Subsystem is the app-level WebRTC egress manager. It sits alongside
// api.API as a sibling — both consume the Restream service, both wire
// themselves into the Echo HTTP router. The subsystem is responsible
// for:
//
//   - Translating the global config.DataWebRTC into the core-level
//     corewebrtc.Config used by the PeerFactory.
//   - Installing ProcessHooks on Restreamer so that per-process start
//     events allocate a pair of UDP ports, create Pion Sources, and
//     inject RTP output legs into the FFmpeg command line.
//   - Serving the WHEP Echo handler (see handler.go).
//
// The zero value is not usable; call New.
type Subsystem struct {
	globalCfg config.DataWebRTC
	coreCfg   corewebrtc.Config
	factory   *corewebrtc.PeerFactory
	logger    log.Logger

	mu      sync.Mutex
	streams map[string]*processStream // processID -> stream pair

	// teardown is set by the Handler (or any other consumer) so the
	// Subsystem can broadcast process-stop events. Called *before*
	// the per-stream Sources are closed, so consumers can yank their
	// own indexes while the stream id is still valid.
	teardown func(streamID string)
}

// processStream captures the two Sources (video + audio) backing a
// single running process's WHEP egress.
type processStream struct {
	id    string
	video *corewebrtc.Source
	audio *corewebrtc.Source
}

// New constructs a Subsystem from the global WebRTC config section.
// The provided ffmpegUDPMax is advisory for logs only (M2 uses the
// OS's ephemeral range via Alloc). Returns an error if the PeerFactory
// cannot be built (e.g., bad NAT1To1 IPs).
func New(dataCfg config.DataWebRTC, logger log.Logger) (*Subsystem, error) {
	if logger == nil {
		logger = log.New("")
	}

	coreCfg := corewebrtc.DefaultConfig()
	coreCfg.Enabled = dataCfg.Enable
	coreCfg.PublicIP = dataCfg.PublicIP

	// If the operator configured multiple NAT1To1 IPs (e.g., dual
	// LAN/public), they take precedence over PublicIP. Wire them
	// through via PublicIP as the first entry; core/webrtc currently
	// reads a single PublicIP, so M2 joins the list with the first
	// entry winning. (Multi-IP NAT1To1 is an M3 enhancement.)
	if len(dataCfg.NAT1To1IPs) > 0 && coreCfg.PublicIP == "" {
		coreCfg.PublicIP = dataCfg.NAT1To1IPs[0]
	}

	factory, err := corewebrtc.NewPeerFactory(coreCfg)
	if err != nil {
		return nil, fmt.Errorf("webrtc subsystem: build peer factory: %w", err)
	}

	return &Subsystem{
		globalCfg: dataCfg,
		coreCfg:   coreCfg,
		factory:   factory,
		logger:    logger.WithComponent("WebRTC"),
		streams:   make(map[string]*processStream),
	}, nil
}

// Enabled reports whether the subsystem should register hooks and
// serve the WHEP endpoint. Called by the API wiring layer to decide
// whether to install anything.
func (s *Subsystem) Enabled() bool {
	return s.globalCfg.Enable
}

// Hooks returns the restream.ProcessHooks the subsystem expects to be
// installed via restream.Restreamer.SetHooks. Exactly one Subsystem
// instance should be installed per Restreamer.
func (s *Subsystem) Hooks() restream.ProcessHooks {
	return restream.ProcessHooks{
		OnStart: s.onProcessStart,
		OnStop:  s.onProcessStop,
	}
}

// Close tears down every active per-process stream. It is safe to
// call during Core shutdown; subsequent WHEP requests will 404.
func (s *Subsystem) Close() {
	s.mu.Lock()
	defer s.mu.Unlock()
	for id, st := range s.streams {
		if st.video != nil {
			_ = st.video.Close()
		}
		if st.audio != nil {
			_ = st.audio.Close()
		}
		delete(s.streams, id)
	}
}

// SetTeardownHook registers a callback invoked just before a stream's
// Sources are closed in onProcessStop. The callback is expected to
// tear down any external resources keyed by streamID — most importantly
// the WHEP Handler's per-stream peer index.
//
// Calling SetTeardownHook again replaces the previous callback; pass
// nil to detach. Only one consumer is supported by design.
func (s *Subsystem) SetTeardownHook(fn func(streamID string)) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.teardown = fn
}

// lookup returns the per-process stream pair for id, or nil, false.
// Used by the WHEP handler.
func (s *Subsystem) lookup(id string) (*processStream, bool) {
	s.mu.Lock()
	defer s.mu.Unlock()
	st, ok := s.streams[id]
	return st, ok
}
