package bind

import (
	"context"
	"net"
	"net/netip"
	"sync"

	log "github.com/sirupsen/logrus"
	"golang.zx2c4.com/wireguard/conn"

	"github.com/netbirdio/netbird/client/iface/udpmux"
)

// RelayBindJS is a conn.Bind implementation for WebAssembly environments.
// Do not limit to build only js, because we want to be able to run tests
type RelayBindJS struct {
	*conn.StdNetBind

	recvChan         chan recvMessage
	endpoints        map[netip.Addr]net.Conn
	endpointsMu      sync.Mutex
	activityRecorder *ActivityRecorder
	ctx              context.Context
	cancel           context.CancelFunc
}

func NewRelayBindJS() *RelayBindJS {
	return &RelayBindJS{
		recvChan:         make(chan recvMessage, 100),
		endpoints:        make(map[netip.Addr]net.Conn),
		activityRecorder: NewActivityRecorder(),
	}
}

// Open creates a receive function for handling relay packets in WASM.
func (s *RelayBindJS) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
	log.Debugf("Open: creating receive function for port %d", uport)

	s.ctx, s.cancel = context.WithCancel(context.Background())

	receiveFn := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (int, error) {
		select {
		case <-s.ctx.Done():
			return 0, net.ErrClosed
		case msg, ok := <-s.recvChan:
			if !ok {
				return 0, net.ErrClosed
			}
			copy(bufs[0], msg.Buffer)
			sizes[0] = len(msg.Buffer)
			eps[0] = conn.Endpoint(msg.Endpoint)
			return 1, nil
		}
	}

	log.Debugf("Open: receive function created, returning port %d", uport)
	return []conn.ReceiveFunc{receiveFn}, uport, nil
}

func (s *RelayBindJS) Close() error {
	if s.cancel == nil {
		return nil
	}
	log.Debugf("close RelayBindJS")
	s.cancel()
	return nil
}

func (s *RelayBindJS) ReceiveFromEndpoint(ctx context.Context, ep *Endpoint, buf []byte) {
	select {
	case <-s.ctx.Done():
		return
	case <-ctx.Done():
		return
	case s.recvChan <- recvMessage{ep, buf}:
	}
}

// Send forwards packets through the relay connection for WASM.
func (s *RelayBindJS) Send(bufs [][]byte, ep conn.Endpoint) error {
	if ep == nil {
		return nil
	}

	fakeIP := ep.DstIP()

	s.endpointsMu.Lock()
	relayConn, ok := s.endpoints[fakeIP]
	s.endpointsMu.Unlock()

	if !ok {
		return nil
	}

	for _, buf := range bufs {
		if _, err := relayConn.Write(buf); err != nil {
			return err
		}
	}

	return nil
}

func (b *RelayBindJS) SetEndpoint(fakeIP netip.Addr, conn net.Conn) {
	b.endpointsMu.Lock()
	b.endpoints[fakeIP] = conn
	b.endpointsMu.Unlock()
}

func (s *RelayBindJS) RemoveEndpoint(fakeIP netip.Addr) {
	s.endpointsMu.Lock()
	defer s.endpointsMu.Unlock()

	delete(s.endpoints, fakeIP)
}

// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
func (s *RelayBindJS) GetICEMux() (*udpmux.UniversalUDPMuxDefault, error) {
	return nil, ErrUDPMUXNotSupported
}

func (s *RelayBindJS) ActivityRecorder() *ActivityRecorder {
	return s.activityRecorder
}
