package uot

import (
	"encoding/binary"
	"io"
	"net"

	"github.com/sagernet/sing/common"
	"github.com/sagernet/sing/common/buf"
	"github.com/sagernet/sing/common/bufio"
	E "github.com/sagernet/sing/common/exceptions"
	M "github.com/sagernet/sing/common/metadata"
	N "github.com/sagernet/sing/common/network"
)

var (
	_ N.NetPacketConn    = (*Conn)(nil)
	_ N.PacketReadWaiter = (*Conn)(nil)
)

type Conn struct {
	net.Conn
	isConnect       bool
	destination     M.Socksaddr
	writer          N.VectorisedWriter
	readWaitOptions N.ReadWaitOptions
}

func NewConn(conn net.Conn, request Request) *Conn {
	uConn := &Conn{
		Conn:        conn,
		isConnect:   request.IsConnect,
		destination: request.Destination,
	}
	uConn.writer, _ = bufio.CreateVectorisedWriter(conn)
	return uConn
}

func (c *Conn) Read(p []byte) (n int, err error) {
	n, _, err = c.ReadFrom(p)
	return
}

func (c *Conn) Write(p []byte) (n int, err error) {
	return c.WriteTo(p, c.destination)
}

func (c *Conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
	var destination M.Socksaddr
	if c.isConnect {
		destination = c.destination
	} else {
		destination, err = AddrParser.ReadAddrPort(c.Conn)
		if err != nil {
			return
		}
	}
	var length uint16
	err = binary.Read(c.Conn, binary.BigEndian, &length)
	if err != nil {
		return
	}
	if len(p) < int(length) {
		err = E.Cause(io.ErrShortBuffer, "UoT read")
		return
	}
	n, err = io.ReadFull(c.Conn, p[:length])
	if err == nil {
		addr = destination.UDPAddr()
	}
	return
}

func (c *Conn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
	destination := M.SocksaddrFromNet(addr)
	var bufferLen int
	if !c.isConnect {
		bufferLen += AddrParser.AddrPortLen(destination)
	}
	bufferLen += 2
	if c.writer == nil {
		bufferLen += len(p)
	}
	buffer := buf.NewSize(bufferLen)
	defer buffer.Release()
	if !c.isConnect {
		err = AddrParser.WriteAddrPort(buffer, destination)
		if err != nil {
			return
		}
	}
	common.Must(binary.Write(buffer, binary.BigEndian, uint16(len(p))))
	if c.writer == nil {
		common.Must1(buffer.Write(p))
		return c.Conn.Write(buffer.Bytes())
	}
	err = c.writer.WriteVectorised([]*buf.Buffer{buffer, buf.As(p)})
	if err == nil {
		n = len(p)
	}
	return
}

func (c *Conn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
	if c.isConnect {
		destination = c.destination
	} else {
		destination, err = AddrParser.ReadAddrPort(c.Conn)
		if err != nil {
			return
		}
	}
	var length uint16
	err = binary.Read(c.Conn, binary.BigEndian, &length)
	if err != nil {
		return
	}
	_, err = buffer.ReadFullFrom(c.Conn, int(length))
	if err != nil {
		return M.Socksaddr{}, E.Cause(err, "UoT read")
	}
	return
}

func (c *Conn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
	var headerLen int
	if !c.isConnect {
		headerLen += AddrParser.AddrPortLen(destination)
	}
	headerLen += 2
	if c.writer == nil {
		headerLen += buffer.Len()
	}
	header := buf.NewSize(headerLen)
	defer header.Release()
	if !c.isConnect {
		err := AddrParser.WriteAddrPort(header, destination)
		if err != nil {
			return err
		}
	}
	common.Must(binary.Write(header, binary.BigEndian, uint16(buffer.Len())))
	if c.writer == nil {
		common.Must1(header.Write(buffer.Bytes()))
		return common.Error(c.Conn.Write(header.Bytes()))
	}
	return c.writer.WriteVectorised([]*buf.Buffer{header, buffer})
}

func (c *Conn) NeedAdditionalReadDeadline() bool {
	return true
}

func (c *Conn) Upstream() any {
	return c.Conn
}
