package socks5

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"net"
	"net/netip"
	"strconv"

	"github.com/Dreamacro/clash/component/auth"

	"github.com/Dreamacro/protobytes"
)

// Error represents a SOCKS error
type Error byte

func (err Error) Error() string {
	return "SOCKS error: " + strconv.Itoa(int(err))
}

// Command is request commands as defined in RFC 1928 section 4.
type Command = uint8

const Version = 5

// SOCKS request commands as defined in RFC 1928 section 4.
const (
	CmdConnect      Command = 1
	CmdBind         Command = 2
	CmdUDPAssociate Command = 3
)

// SOCKS address types as defined in RFC 1928 section 5.
const (
	AtypIPv4       = 1
	AtypDomainName = 3
	AtypIPv6       = 4
)

// MaxAddrLen is the maximum size of SOCKS address in bytes.
const MaxAddrLen = 1 + 1 + 255 + 2

// MaxAuthLen is the maximum size of user/password field in SOCKS5 Auth
const MaxAuthLen = 255

// Addr represents a SOCKS address as defined in RFC 1928 section 5.
type Addr []byte

func (a Addr) String() string {
	var host, port string

	switch a[0] {
	case AtypDomainName:
		hostLen := uint16(a[1])
		host = string(a[2 : 2+hostLen])
		port = strconv.Itoa((int(a[2+hostLen]) << 8) | int(a[2+hostLen+1]))
	case AtypIPv4:
		host = net.IP(a[1 : 1+net.IPv4len]).String()
		port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1]))
	case AtypIPv6:
		host = net.IP(a[1 : 1+net.IPv6len]).String()
		port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1]))
	}

	return net.JoinHostPort(host, port)
}

// UDPAddr converts a socks5.Addr to *net.UDPAddr
func (a Addr) UDPAddr() *net.UDPAddr {
	if len(a) == 0 {
		return nil
	}
	switch a[0] {
	case AtypIPv4:
		var ip [net.IPv4len]byte
		copy(ip[0:], a[1:1+net.IPv4len])
		return &net.UDPAddr{IP: net.IP(ip[:]), Port: int(binary.BigEndian.Uint16(a[1+net.IPv4len : 1+net.IPv4len+2]))}
	case AtypIPv6:
		var ip [net.IPv6len]byte
		copy(ip[0:], a[1:1+net.IPv6len])
		return &net.UDPAddr{IP: net.IP(ip[:]), Port: int(binary.BigEndian.Uint16(a[1+net.IPv6len : 1+net.IPv6len+2]))}
	}
	// Other Atyp
	return nil
}

// SOCKS errors as defined in RFC 1928 section 6.
const (
	ErrGeneralFailure       = Error(1)
	ErrConnectionNotAllowed = Error(2)
	ErrNetworkUnreachable   = Error(3)
	ErrHostUnreachable      = Error(4)
	ErrConnectionRefused    = Error(5)
	ErrTTLExpired           = Error(6)
	ErrCommandNotSupported  = Error(7)
	ErrAddressNotSupported  = Error(8)
)

// Auth errors used to return a specific "Auth failed" error
var ErrAuth = errors.New("auth failed")

type User struct {
	Username string
	Password string
}

// ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side.
func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, command Command, err error) {
	// Read RFC 1928 for request and reply structure and sizes.
	buf := make([]byte, MaxAddrLen)
	// read VER, NMETHODS, METHODS
	if _, err = io.ReadFull(rw, buf[:2]); err != nil {
		return
	}
	nmethods := buf[1]
	if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil {
		return
	}

	// write VER METHOD
	if authenticator != nil {
		if _, err = rw.Write([]byte{5, 2}); err != nil {
			return
		}

		// Get header
		header := make([]byte, 2)
		if _, err = io.ReadFull(rw, header); err != nil {
			return
		}

		authBuf := make([]byte, MaxAuthLen)
		// Get username
		userLen := int(header[1])
		if userLen <= 0 {
			rw.Write([]byte{1, 1})
			err = ErrAuth
			return
		}
		if _, err = io.ReadFull(rw, authBuf[:userLen]); err != nil {
			return
		}
		user := string(authBuf[:userLen])

		// Get password
		if _, err = rw.Read(header[:1]); err != nil {
			return
		}
		passLen := int(header[0])
		if passLen <= 0 {
			rw.Write([]byte{1, 1})
			err = ErrAuth
			return
		}
		if _, err = io.ReadFull(rw, authBuf[:passLen]); err != nil {
			return
		}
		pass := string(authBuf[:passLen])

		// Verify
		if ok := authenticator.Verify(user, pass); !ok {
			rw.Write([]byte{1, 1})
			err = ErrAuth
			return
		}

		// Response auth state
		if _, err = rw.Write([]byte{1, 0}); err != nil {
			return
		}
	} else {
		if _, err = rw.Write([]byte{5, 0}); err != nil {
			return
		}
	}

	// read VER CMD RSV ATYP DST.ADDR DST.PORT
	if _, err = io.ReadFull(rw, buf[:3]); err != nil {
		return
	}

	command = buf[1]
	addr, err = ReadAddr(rw, buf)
	if err != nil {
		return
	}

	switch command {
	case CmdConnect, CmdUDPAssociate:
		// Acquire server listened address info
		localAddr := ParseAddr(rw.LocalAddr().String())
		if localAddr == nil {
			err = ErrAddressNotSupported
		} else {
			// write VER REP RSV ATYP BND.ADDR BND.PORT
			_, err = rw.Write(bytes.Join([][]byte{{5, 0, 0}, localAddr}, []byte{}))
		}
	case CmdBind:
		fallthrough
	default:
		err = ErrCommandNotSupported
	}

	return
}

// ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side.
func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (Addr, error) {
	buf := make([]byte, MaxAddrLen)
	var err error

	// VER, NMETHODS, METHODS
	if user != nil {
		_, err = rw.Write([]byte{5, 1, 2})
	} else {
		_, err = rw.Write([]byte{5, 1, 0})
	}
	if err != nil {
		return nil, err
	}

	// VER, METHOD
	if _, err := io.ReadFull(rw, buf[:2]); err != nil {
		return nil, err
	}

	if buf[0] != 5 {
		return nil, errors.New("SOCKS version error")
	}

	if buf[1] == 2 {
		if user == nil {
			return nil, ErrAuth
		}

		// password protocol version
		authMsg := protobytes.BytesWriter{}
		authMsg.PutUint8(1)
		authMsg.PutUint8(uint8(len(user.Username)))
		authMsg.PutString(user.Username)
		authMsg.PutUint8(uint8(len(user.Password)))
		authMsg.PutString(user.Password)

		if _, err := rw.Write(authMsg.Bytes()); err != nil {
			return nil, err
		}

		if _, err := io.ReadFull(rw, buf[:2]); err != nil {
			return nil, err
		}

		if buf[1] != 0 {
			return nil, errors.New("rejected username/password")
		}
	} else if buf[1] != 0 {
		return nil, errors.New("SOCKS need auth")
	}

	// VER, CMD, RSV, ADDR
	if _, err := rw.Write(bytes.Join([][]byte{{5, command, 0}, addr}, []byte{})); err != nil {
		return nil, err
	}

	// VER, REP, RSV
	if _, err := io.ReadFull(rw, buf[:3]); err != nil {
		return nil, err
	}

	return ReadAddr(rw, buf)
}

func ReadAddr(r io.Reader, b []byte) (Addr, error) {
	if len(b) < MaxAddrLen {
		return nil, io.ErrShortBuffer
	}
	_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
	if err != nil {
		return nil, err
	}

	switch b[0] {
	case AtypDomainName:
		_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
		if err != nil {
			return nil, err
		}
		domainLength := uint16(b[1])
		_, err = io.ReadFull(r, b[2:2+domainLength+2])
		return b[:1+1+domainLength+2], err
	case AtypIPv4:
		_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
		return b[:1+net.IPv4len+2], err
	case AtypIPv6:
		_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
		return b[:1+net.IPv6len+2], err
	}

	return nil, ErrAddressNotSupported
}

// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
func SplitAddr(b []byte) Addr {
	addrLen := 1
	if len(b) < addrLen {
		return nil
	}

	switch b[0] {
	case AtypDomainName:
		if len(b) < 2 {
			return nil
		}
		addrLen = 1 + 1 + int(b[1]) + 2
	case AtypIPv4:
		addrLen = 1 + net.IPv4len + 2
	case AtypIPv6:
		addrLen = 1 + net.IPv6len + 2
	default:
		return nil

	}

	if len(b) < addrLen {
		return nil
	}

	return b[:addrLen]
}

// ParseAddr parses the address in string s. Returns nil if failed.
func ParseAddr(s string) Addr {
	buf := protobytes.BytesWriter{}
	host, port, err := net.SplitHostPort(s)
	if err != nil {
		return nil
	}
	if ip := net.ParseIP(host); ip != nil {
		if ip4 := ip.To4(); ip4 != nil {
			buf.PutUint8(AtypIPv4)
			buf.PutSlice(ip4)
		} else {
			buf.PutUint8(AtypIPv6)
			buf.PutSlice(ip)
		}
	} else {
		if len(host) > 255 {
			return nil
		}
		buf.PutUint8(AtypDomainName)
		buf.PutUint8(byte(len(host)))
		buf.PutString(host)
	}

	portnum, err := strconv.ParseUint(port, 10, 16)
	if err != nil {
		return nil
	}

	buf.PutUint16be(uint16(portnum))
	return Addr(buf.Bytes())
}

// ParseAddrToSocksAddr parse a socks addr from net.addr
// This is a fast path of ParseAddr(addr.String())
func ParseAddrToSocksAddr(addr net.Addr) Addr {
	var hostip net.IP
	var port int
	if udpaddr, ok := addr.(*net.UDPAddr); ok {
		hostip = udpaddr.IP
		port = udpaddr.Port
	} else if tcpaddr, ok := addr.(*net.TCPAddr); ok {
		hostip = tcpaddr.IP
		port = tcpaddr.Port
	}

	// fallback parse
	if hostip == nil {
		return ParseAddr(addr.String())
	}

	var parsed protobytes.BytesWriter
	if ip4 := hostip.To4(); ip4.DefaultMask() != nil {
		parsed = make([]byte, 0, 1+net.IPv4len+2)
		parsed.PutUint8(AtypIPv4)
		parsed.PutSlice(ip4)
		parsed.PutUint16be(uint16(port))
	} else {
		parsed = make([]byte, 0, 1+net.IPv6len+2)
		parsed.PutUint8(AtypIPv6)
		parsed.PutSlice(hostip)
		parsed.PutUint16be(uint16(port))
	}
	return Addr(parsed)
}

func AddrFromStdAddrPort(addrPort netip.AddrPort) Addr {
	addr := addrPort.Addr()
	if addr.Is4() {
		ip4 := addr.As4()
		return []byte{AtypIPv4, ip4[0], ip4[1], ip4[2], ip4[3], byte(addrPort.Port() >> 8), byte(addrPort.Port())}
	}

	buf := make([]byte, 1+net.IPv6len+2)
	buf[0] = AtypIPv6
	copy(buf[1:], addr.AsSlice())
	buf[1+net.IPv6len] = byte(addrPort.Port() >> 8)
	buf[1+net.IPv6len+1] = byte(addrPort.Port())
	return buf
}

// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) {
	r := protobytes.BytesReader(packet)

	if r.Len() < 5 {
		err = errors.New("insufficient length of packet")
		return
	}

	// packet[0] and packet[1] are reserved
	reserved, r := r.SplitAt(2)
	if !bytes.Equal(reserved, []byte{0, 0}) {
		err = errors.New("reserved fields should be zero")
		return
	}

	if r.ReadUint8() != 0 /* fragments */ {
		err = errors.New("discarding fragmented payload")
		return
	}

	addr = SplitAddr(r)
	if addr == nil {
		err = errors.New("failed to read UDP header")
	}

	_, payload = r.SplitAt(len(addr))
	return
}

func EncodeUDPPacket(addr Addr, payload []byte) (packet []byte, err error) {
	if addr == nil {
		err = errors.New("address is invalid")
		return
	}
	w := protobytes.BytesWriter{}
	w.PutSlice([]byte{0, 0, 0})
	w.PutSlice(addr)
	w.PutSlice(payload)
	packet = w.Bytes()
	return
}
