//go:build with_lwip

package tun

import (
	"context"
	"net"
	"net/netip"
	"os"

	lwip "github.com/sagernet/go-tun2socks/core"
	"github.com/sagernet/sing/common"
	"github.com/sagernet/sing/common/buf"
	M "github.com/sagernet/sing/common/metadata"
	N "github.com/sagernet/sing/common/network"
	"github.com/sagernet/sing/common/udpnat"
)

type LWIP struct {
	ctx        context.Context
	tun        Tun
	tunMtu     uint32
	udpTimeout int64
	handler    Handler
	stack      lwip.LWIPStack
	udpNat     *udpnat.Service[netip.AddrPort]
}

func NewLWIP(
	options StackOptions,
) (Stack, error) {
	return &LWIP{
		ctx:     options.Context,
		tun:     options.Tun,
		tunMtu:  options.MTU,
		handler: options.Handler,
		stack:   lwip.NewLWIPStack(),
		udpNat:  udpnat.New[netip.AddrPort](options.UDPTimeout, options.Handler),
	}, nil
}

func (l *LWIP) Start() error {
	lwip.RegisterTCPConnHandler(l)
	lwip.RegisterUDPConnHandler(l)
	lwip.RegisterOutputFn(l.tun.Write)
	go l.loopIn()
	return nil
}

func (l *LWIP) loopIn() {
	if winTun, isWintun := l.tun.(WinTun); isWintun {
		l.loopInWintun(winTun)
		return
	}
	buffer := make([]byte, int(l.tunMtu)+PacketOffset)
	for {
		n, err := l.tun.Read(buffer)
		if err != nil {
			return
		}
		_, err = l.stack.Write(buffer[PacketOffset:n])
		if err != nil {
			if err.Error() == "stack closed" {
				return
			}
			l.handler.NewError(context.Background(), err)
		}
	}
}

func (l *LWIP) loopInWintun(tun WinTun) {
	for {
		packet, release, err := tun.ReadPacket()
		if err != nil {
			return
		}
		_, err = l.stack.Write(packet)
		release()
		if err != nil {
			if err.Error() == "stack closed" {
				return
			}
			l.handler.NewError(context.Background(), err)
		}
	}
}

func (l *LWIP) Close() error {
	lwip.RegisterTCPConnHandler(nil)
	lwip.RegisterUDPConnHandler(nil)
	lwip.RegisterOutputFn(func(bytes []byte) (int, error) {
		return 0, os.ErrClosed
	})
	return l.stack.Close()
}

func (l *LWIP) Handle(conn net.Conn) error {
	lAddr := conn.LocalAddr()
	rAddr := conn.RemoteAddr()
	if lAddr == nil || rAddr == nil {
		conn.Close()
		return nil
	}
	go func() {
		var metadata M.Metadata
		metadata.Source = M.SocksaddrFromNet(lAddr)
		metadata.Destination = M.SocksaddrFromNet(rAddr)
		hErr := l.handler.NewConnection(l.ctx, conn, metadata)
		if hErr != nil {
			conn.(lwip.TCPConn).Abort()
		}
	}()
	return nil
}

func (l *LWIP) ReceiveTo(conn lwip.UDPConn, data []byte, addr M.Socksaddr) error {
	var upstreamMetadata M.Metadata
	upstreamMetadata.Source = conn.LocalAddr()
	upstreamMetadata.Destination = addr

	l.udpNat.NewPacket(
		l.ctx,
		upstreamMetadata.Source.AddrPort(),
		buf.As(data).ToOwned(),
		upstreamMetadata,
		func(natConn N.PacketConn) N.PacketWriter {
			return &LWIPUDPBackWriter{conn}
		},
	)
	return nil
}

type LWIPUDPBackWriter struct {
	conn lwip.UDPConn
}

func (w *LWIPUDPBackWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
	defer buffer.Release()
	return common.Error(w.conn.WriteFrom(buffer.Bytes(), destination))
}

func (w *LWIPUDPBackWriter) Close() error {
	return w.conn.Close()
}
