package protocol

import (
	"encoding/binary"
	"errors"
	"fmt"
	"net/netip"
)

const (
	VPNCmdOpen  byte = 0x30
	VPNCmdPush  byte = 0x31
	VPNCmdPull  byte = 0x32
	VPNCmdClose byte = 0x33

	VPNRespOpened byte = 0x40
	VPNRespAck    byte = 0x41
	VPNRespData   byte = 0x42
	VPNRespWait   byte = 0x43
	VPNRespClosed byte = 0x44
	VPNRespError  byte = 0x7f

	VPNNoAck uint32 = 0xffffffff

	// Raw IP packets remain bounded by the IPv4/IPv6 packet-length model.
	VPNMaxPacket = 65535

	// DragonTCP transfer objects/records are independent of IP packet size.
	// Multiple IP packets may be batched into one transfer object.
	VPNMaxFragment = 1024 * 1024
	VPNMaxBatch    = 1024 * 1024

	VPNBatchVersion byte = 1
)

type VPNSessionID [16]byte

func VPNError(message string) []byte {
	b := []byte(message)
	if len(b) > 4096 {
		b = b[:4096]
	}
	out := make([]byte, 1+len(b))
	out[0] = VPNRespError
	copy(out[1:], b)
	return out
}

func ParseVPNError(payload []byte) error {
	if len(payload) == 0 {
		return errors.New("empty DragonTCP VPN response")
	}
	if payload[0] == VPNRespError {
		return errors.New(string(payload[1:]))
	}
	return nil
}

// OPEN request:
// cmd(1) sid(16) tokenLen(2) token(N) ipv4(4) ipv6(16) mtu(2)
func BuildVPNOpen(sid VPNSessionID, token string, ipv4, ipv6 netip.Addr, mtu int) ([]byte, error) {
	if len(token) > 4096 {
		return nil, errors.New("token too long")
	}
	if !ipv4.Is4() || !ipv6.Is6() {
		return nil, errors.New("invalid VPN client addresses")
	}
	if mtu < 576 || mtu > VPNMaxPacket {
		return nil, errors.New("invalid VPN MTU")
	}
	out := make([]byte, 1+16+2+len(token)+4+16+2)
	out[0] = VPNCmdOpen
	copy(out[1:17], sid[:])
	binary.BigEndian.PutUint16(out[17:19], uint16(len(token)))
	pos := 19
	copy(out[pos:pos+len(token)], token)
	pos += len(token)
	v4 := ipv4.As4()
	copy(out[pos:pos+4], v4[:])
	pos += 4
	v6 := ipv6.As16()
	copy(out[pos:pos+16], v6[:])
	pos += 16
	binary.BigEndian.PutUint16(out[pos:pos+2], uint16(mtu))
	return out, nil
}

func ParseVPNOpen(payload []byte) (sid VPNSessionID, token string, ipv4, ipv6 netip.Addr, mtu int, err error) {
	if len(payload) < 1+16+2+4+16+2 || payload[0] != VPNCmdOpen {
		err = errors.New("bad VPN OPEN")
		return
	}
	copy(sid[:], payload[1:17])
	tokenLen := int(binary.BigEndian.Uint16(payload[17:19]))
	need := 1 + 16 + 2 + tokenLen + 4 + 16 + 2
	if len(payload) != need {
		err = errors.New("bad VPN OPEN length")
		return
	}
	pos := 19
	token = string(payload[pos : pos+tokenLen])
	pos += tokenLen
	var a4 [4]byte
	copy(a4[:], payload[pos:pos+4])
	ipv4 = netip.AddrFrom4(a4)
	pos += 4
	var a6 [16]byte
	copy(a6[:], payload[pos:pos+16])
	ipv6 = netip.AddrFrom16(a6)
	pos += 16
	mtu = int(binary.BigEndian.Uint16(payload[pos : pos+2]))
	return
}

// OPENED v2 response: cmd(1) maxChunk(4).
// ParseVPNOpened also accepts the old 3-byte/uint16 response for compatibility.
func BuildVPNOpened(maxChunk int) []byte {
	if maxChunk > VPNMaxFragment {
		maxChunk = VPNMaxFragment
	}
	if maxChunk < 1 {
		maxChunk = 1
	}
	out := make([]byte, 5)
	out[0] = VPNRespOpened
	binary.BigEndian.PutUint32(out[1:5], uint32(maxChunk))
	return out
}

func ParseVPNOpened(payload []byte) (int, error) {
	if err := ParseVPNError(payload); err != nil {
		return 0, err
	}
	if len(payload) == 5 && payload[0] == VPNRespOpened {
		v := int(binary.BigEndian.Uint32(payload[1:5]))
		if v < 1 || v > VPNMaxFragment {
			return 0, errors.New("bad VPN OPENED max chunk")
		}
		return v, nil
	}
	if len(payload) == 3 && payload[0] == VPNRespOpened {
		return int(binary.BigEndian.Uint16(payload[1:3])), nil
	}
	return 0, errors.New("bad VPN OPENED response")
}

// PUSH v2 request: cmd(1) sid(16) seq(4) offset(4) total(4) data(N)
func BuildVPNPush(sid VPNSessionID, seq uint32, offset, total int, data []byte) ([]byte, error) {
	if total < 1 || total > VPNMaxBatch || offset < 0 || offset > total || len(data) < 1 || offset+len(data) > total || len(data) > VPNMaxFragment {
		return nil, errors.New("invalid VPN PUSH fragment")
	}
	out := make([]byte, 29+len(data))
	out[0] = VPNCmdPush
	copy(out[1:17], sid[:])
	binary.BigEndian.PutUint32(out[17:21], seq)
	binary.BigEndian.PutUint32(out[21:25], uint32(offset))
	binary.BigEndian.PutUint32(out[25:29], uint32(total))
	copy(out[29:], data)
	return out, nil
}

func ParseVPNPush(payload []byte) (sid VPNSessionID, seq uint32, offset, total int, data []byte, err error) {
	if len(payload) < 30 || payload[0] != VPNCmdPush {
		err = errors.New("bad VPN PUSH")
		return
	}
	copy(sid[:], payload[1:17])
	seq = binary.BigEndian.Uint32(payload[17:21])
	offset = int(binary.BigEndian.Uint32(payload[21:25]))
	total = int(binary.BigEndian.Uint32(payload[25:29]))
	data = payload[29:]
	if total < 1 || total > VPNMaxBatch || offset < 0 || offset > total || len(data) < 1 || len(data) > VPNMaxFragment || offset+len(data) > total {
		err = errors.New("bad VPN PUSH fragment bounds")
	}
	return
}

func BuildVPNAck(seq uint32, accepted int) []byte {
	out := make([]byte, 9)
	out[0] = VPNRespAck
	binary.BigEndian.PutUint32(out[1:5], seq)
	binary.BigEndian.PutUint32(out[5:9], uint32(accepted))
	return out
}

func ParseVPNAck(payload []byte) (seq uint32, accepted int, err error) {
	if e := ParseVPNError(payload); e != nil {
		err = e
		return
	}
	if len(payload) != 9 || payload[0] != VPNRespAck {
		err = errors.New("bad VPN ACK")
		return
	}
	seq = binary.BigEndian.Uint32(payload[1:5])
	accepted = int(binary.BigEndian.Uint32(payload[5:9]))
	return
}

// PULL v2 request: cmd(1) sid(16) ack(4) want(4) offset(4) limit(4)
func BuildVPNPull(sid VPNSessionID, ack, want uint32, offset, limit int) ([]byte, error) {
	if offset < 0 || offset > VPNMaxBatch || limit < 1 || limit > VPNMaxFragment {
		return nil, errors.New("invalid VPN PULL")
	}
	out := make([]byte, 33)
	out[0] = VPNCmdPull
	copy(out[1:17], sid[:])
	binary.BigEndian.PutUint32(out[17:21], ack)
	binary.BigEndian.PutUint32(out[21:25], want)
	binary.BigEndian.PutUint32(out[25:29], uint32(offset))
	binary.BigEndian.PutUint32(out[29:33], uint32(limit))
	return out, nil
}

func ParseVPNPull(payload []byte) (sid VPNSessionID, ack, want uint32, offset, limit int, err error) {
	if len(payload) != 33 || payload[0] != VPNCmdPull {
		err = errors.New("bad VPN PULL")
		return
	}
	copy(sid[:], payload[1:17])
	ack = binary.BigEndian.Uint32(payload[17:21])
	want = binary.BigEndian.Uint32(payload[21:25])
	offset = int(binary.BigEndian.Uint32(payload[25:29]))
	limit = int(binary.BigEndian.Uint32(payload[29:33]))
	if offset < 0 || offset > VPNMaxBatch || limit < 1 || limit > VPNMaxFragment {
		err = errors.New("bad VPN PULL bounds")
	}
	return
}

// DATA v2 response: cmd(1) seq(4) offset(4) total(4) data(N)
func BuildVPNData(seq uint32, offset, total int, data []byte) []byte {
	out := make([]byte, 13+len(data))
	out[0] = VPNRespData
	binary.BigEndian.PutUint32(out[1:5], seq)
	binary.BigEndian.PutUint32(out[5:9], uint32(offset))
	binary.BigEndian.PutUint32(out[9:13], uint32(total))
	copy(out[13:], data)
	return out
}

func ParseVPNData(payload []byte) (seq uint32, offset, total int, data []byte, wait bool, err error) {
	if e := ParseVPNError(payload); e != nil {
		err = e
		return
	}
	if len(payload) == 1 && payload[0] == VPNRespWait {
		wait = true
		return
	}
	if len(payload) < 14 || payload[0] != VPNRespData {
		err = fmt.Errorf("bad VPN DATA response type/length")
		return
	}
	seq = binary.BigEndian.Uint32(payload[1:5])
	offset = int(binary.BigEndian.Uint32(payload[5:9]))
	total = int(binary.BigEndian.Uint32(payload[9:13]))
	data = payload[13:]
	if total < 1 || total > VPNMaxBatch || offset < 0 || offset+len(data) > total || len(data) < 1 || len(data) > VPNMaxFragment {
		err = errors.New("bad VPN DATA bounds")
	}
	return
}

// A transfer object is a batch of raw IP packets:
// version(1), then repeated packetLen(2) + packet bytes.
func BuildVPNBatch(packets [][]byte) ([]byte, error) {
	if len(packets) == 0 {
		return nil, errors.New("empty VPN batch")
	}
	total := 1
	for _, packet := range packets {
		if len(packet) < 1 || len(packet) > VPNMaxPacket {
			return nil, errors.New("invalid IP packet length in VPN batch")
		}
		total += 2 + len(packet)
		if total > VPNMaxBatch {
			return nil, errors.New("VPN batch exceeds maximum")
		}
	}
	out := make([]byte, total)
	out[0] = VPNBatchVersion
	pos := 1
	for _, packet := range packets {
		binary.BigEndian.PutUint16(out[pos:pos+2], uint16(len(packet)))
		pos += 2
		copy(out[pos:pos+len(packet)], packet)
		pos += len(packet)
	}
	return out, nil
}

func ParseVPNBatch(batch []byte) ([][]byte, error) {
	if len(batch) < 4 || len(batch) > VPNMaxBatch || batch[0] != VPNBatchVersion {
		return nil, errors.New("bad VPN batch")
	}
	packets := make([][]byte, 0, 8)
	pos := 1
	for pos < len(batch) {
		if pos+2 > len(batch) {
			return nil, errors.New("truncated VPN batch packet length")
		}
		n := int(binary.BigEndian.Uint16(batch[pos : pos+2]))
		pos += 2
		if n < 1 || n > VPNMaxPacket || pos+n > len(batch) {
			return nil, errors.New("invalid VPN batch packet")
		}
		packet := make([]byte, n)
		copy(packet, batch[pos:pos+n])
		packets = append(packets, packet)
		pos += n
	}
	if len(packets) == 0 {
		return nil, errors.New("VPN batch contains no packets")
	}
	return packets, nil
}

// PacketAddresses returns the source and destination addresses from a raw
// IPv4/IPv6 packet. The packet may contain trailing bytes; the IP header's own
// length field is validated against the supplied buffer.
func PacketAddresses(packet []byte) (src, dst netip.Addr, err error) {
	if len(packet) < 1 {
		return src, dst, errors.New("empty IP packet")
	}
	switch packet[0] >> 4 {
	case 4:
		if len(packet) < 20 {
			return src, dst, errors.New("short IPv4 packet")
		}
		total := int(packet[2])<<8 | int(packet[3])
		if total < 20 || total > len(packet) {
			return src, dst, errors.New("invalid IPv4 total length")
		}
		var a, b [4]byte
		copy(a[:], packet[12:16])
		copy(b[:], packet[16:20])
		return netip.AddrFrom4(a), netip.AddrFrom4(b), nil
	case 6:
		if len(packet) < 40 {
			return src, dst, errors.New("short IPv6 packet")
		}
		total := 40 + (int(packet[4])<<8 | int(packet[5]))
		if total > len(packet) {
			return src, dst, errors.New("invalid IPv6 payload length")
		}
		var a, b [16]byte
		copy(a[:], packet[8:24])
		copy(b[:], packet[24:40])
		return netip.AddrFrom16(a), netip.AddrFrom16(b), nil
	default:
		return src, dst, errors.New("unsupported IP version")
	}
}

func BuildVPNClose(sid VPNSessionID) []byte {
	out := make([]byte, 17)
	out[0] = VPNCmdClose
	copy(out[1:17], sid[:])
	return out
}

func ParseVPNClose(payload []byte) (sid VPNSessionID, err error) {
	if len(payload) != 17 || payload[0] != VPNCmdClose {
		return sid, errors.New("bad VPN CLOSE")
	}
	copy(sid[:], payload[1:17])
	return sid, nil
}

func IsVPNCommand(payload []byte) bool {
	if len(payload) == 0 {
		return false
	}
	return payload[0] >= VPNCmdOpen && payload[0] <= VPNCmdClose
}
