package udp

import (
	"bytes"
	"encoding/binary"
	"eq2emu/internal/common/opcodes"
	"errors"
	"fmt"
)

// Common protocol errors
var (
	ErrPacketTooSmall = errors.New("packet too small")
	ErrInvalidCRC     = errors.New("invalid CRC")
	ErrInvalidOpcode  = errors.New("invalid opcode")
)

// ProtocolPacket represents a low-level UDP protocol packet with opcode and payload
type ProtocolPacket struct {
	Opcode uint8  // Protocol operation code (1-2 bytes when serialized)
	Data   []byte // Packet payload data
	Raw    []byte // Original raw packet data for debugging
}

// ApplicationPacket represents a higher-level game application packet
type ApplicationPacket struct {
	Opcode uint16 // Application-level operation code
	Data   []byte // Application payload data
}

// ParseProtocolPacket parses raw UDP data into a ProtocolPacket
// Handles variable opcode sizing and CRC validation based on EQ2 protocol
func ParseProtocolPacket(data []byte) (*ProtocolPacket, error) {
	if len(data) < 2 {
		return nil, ErrPacketTooSmall
	}

	var opcode uint8
	var dataStart int

	// EQ2 protocol uses 1-byte opcodes normally, 2-byte for opcodes >= 0xFF
	// When opcode >= 0xFF, it's prefixed with 0x00
	if data[0] == 0x00 && len(data) > 2 {
		opcode = data[1]
		dataStart = 2
	} else {
		opcode = data[0]
		dataStart = 1
	}

	// Extract payload, handling CRC for non-session packets
	var payload []byte
	if requiresCRC(opcode) {
		if len(data) < dataStart+2 {
			return nil, ErrPacketTooSmall
		}

		// Payload excludes the 2-byte CRC suffix
		payload = data[dataStart : len(data)-2]

		// Validate CRC on the entire packet from beginning
		if !ValidateCRC(data) {
			return nil, fmt.Errorf("%w for opcode 0x%02X", ErrInvalidCRC, opcode)
		}
	} else {
		payload = data[dataStart:]
	}

	return &ProtocolPacket{
		Opcode: opcode,
		Data:   payload,
		Raw:    data,
	}, nil
}

// Serialize converts ProtocolPacket back to wire format with proper opcode encoding and CRC
func (p *ProtocolPacket) Serialize() []byte {
	var result []byte

	// Handle variable opcode encoding
	if p.Opcode == 0xFF {
		// 2-byte opcode format: [0x00][actual_opcode][data]
		result = make([]byte, 2+len(p.Data))
		result[0] = 0x00
		result[1] = p.Opcode
		copy(result[2:], p.Data)
	} else {
		// 1-byte opcode format: [opcode][data]
		result = make([]byte, 1+len(p.Data))
		result[0] = p.Opcode
		copy(result[1:], p.Data)
	}

	// Add CRC for packets that require it
	if requiresCRC(p.Opcode) {
		result = AppendCRC(result)
	}

	return result
}

// String provides human-readable representation for debugging
func (p *ProtocolPacket) String() string {
	return fmt.Sprintf("ProtocolPacket{Opcode: 0x%02X, DataLen: %d}", p.Opcode, len(p.Data))
}

// ParseApplicationPacket parses application-level packet from decrypted/decompressed data
func ParseApplicationPacket(data []byte) (*ApplicationPacket, error) {
	if len(data) < 2 {
		return nil, errors.New("application packet requires at least 2 bytes for opcode")
	}

	// Application opcodes are always little-endian 16-bit values
	opcode := binary.LittleEndian.Uint16(data[0:2])

	return &ApplicationPacket{
		Opcode: opcode,
		Data:   data[2:],
	}, nil
}

// Serialize converts ApplicationPacket to byte array for transmission
func (p *ApplicationPacket) Serialize() []byte {
	result := make([]byte, 2+len(p.Data))
	binary.LittleEndian.PutUint16(result[0:2], p.Opcode)
	copy(result[2:], p.Data)
	return result
}

// String provides human-readable representation for debugging
func (p *ApplicationPacket) String() string {
	return fmt.Sprintf("ApplicationPacket{Opcode: 0x%04X, DataLen: %d}", p.Opcode, len(p.Data))
}

// requiresCRC determines if a protocol opcode requires CRC validation
// Session control packets (SessionRequest, SessionResponse, OutOfSession) don't use CRC
func requiresCRC(opcode uint8) bool {
	switch opcode {
	case opcodes.OpSessionRequest, opcodes.OpSessionResponse, opcodes.OpOutOfSession:
		return false
	default:
		return true
	}
}

// PacketCombiner groups small packets together to reduce UDP overhead
type PacketCombiner struct {
	PendingPackets []*ProtocolPacket // Direct access to pending packets
	MaxSize        int               // Direct access to max size
}

// NewPacketCombiner creates a combiner with specified max size
func NewPacketCombiner(maxSize int) *PacketCombiner {
	return &PacketCombiner{
		MaxSize: maxSize,
	}
}

// Add queues a packet for potential combining
func (pc *PacketCombiner) Add(packet *ProtocolPacket) {
	pc.PendingPackets = append(pc.PendingPackets, packet)
}

// Flush returns combined packets and clears the queue
func (pc *PacketCombiner) Flush() []*ProtocolPacket {
	count := len(pc.PendingPackets)
	if count == 0 {
		return nil
	}

	if count == 1 {
		// Single packet - no combining needed
		packet := pc.PendingPackets[0]
		pc.Clear()
		return []*ProtocolPacket{packet}
	}

	// Combine multiple packets
	combined := pc.combine()
	pc.Clear()
	return []*ProtocolPacket{combined}
}

// combine merges all pending packets into a single combined packet
func (pc *PacketCombiner) combine() *ProtocolPacket {
	var buf bytes.Buffer

	for _, packet := range pc.PendingPackets {
		serialized := packet.Serialize()
		pc.writeSizeHeader(&buf, len(serialized))
		buf.Write(serialized)
	}

	return &ProtocolPacket{
		Opcode: opcodes.OpCombined,
		Data:   buf.Bytes(),
	}
}

// writeSizeHeader writes packet size using variable-length encoding
func (pc *PacketCombiner) writeSizeHeader(buf *bytes.Buffer, size int) {
	if size >= 255 {
		// Large packet - use 3-byte header [0xFF][low][high]
		buf.WriteByte(0xFF)
		buf.WriteByte(byte(size))
		buf.WriteByte(byte(size >> 8))
	} else {
		// Small packet - use 1-byte header
		buf.WriteByte(byte(size))
	}
}

// ShouldCombine determines if packets should be combined based on total size
func (pc *PacketCombiner) ShouldCombine() bool {
	if len(pc.PendingPackets) < 2 {
		return false
	}

	totalSize := 0
	for _, packet := range pc.PendingPackets {
		serialized := packet.Serialize()
		totalSize += len(serialized)

		// Add size header overhead
		if len(serialized) >= 255 {
			totalSize += 3
		} else {
			totalSize += 1
		}
	}

	return totalSize <= pc.MaxSize
}

// Clear removes all pending packets
func (pc *PacketCombiner) Clear() {
	pc.PendingPackets = pc.PendingPackets[:0] // Reuse slice capacity
}

// ParseCombinedPacket splits combined packet into individual packets
func ParseCombinedPacket(data []byte) ([]*ProtocolPacket, error) {
	var packets []*ProtocolPacket
	offset := 0

	for offset < len(data) {
		size, headerSize, err := readSizeHeader(data, offset)
		if err != nil {
			break
		}

		offset += headerSize

		if offset+size > len(data) {
			break // Incomplete packet
		}

		// Parse individual packet
		packetData := data[offset : offset+size]
		if packet, err := ParseProtocolPacket(packetData); err == nil {
			packets = append(packets, packet)
		}

		offset += size
	}

	return packets, nil
}

// readSizeHeader reads variable-length size header
func readSizeHeader(data []byte, offset int) (size, headerSize int, err error) {
	if offset >= len(data) {
		return 0, 0, errors.New("insufficient data")
	}

	if data[offset] == 0xFF {
		// 3-byte size header
		if offset+2 >= len(data) {
			return 0, 0, errors.New("insufficient data for 3-byte header")
		}
		size = int(data[offset+1]) | (int(data[offset+2]) << 8)
		headerSize = 3
	} else {
		// 1-byte size header
		size = int(data[offset])
		headerSize = 1
	}

	return size, headerSize, nil
}

// ValidateCombinedPacket checks if combined packet data is well-formed
func ValidateCombinedPacket(data []byte) error {
	offset := 0
	count := 0

	for offset < len(data) {
		size, headerSize, err := readSizeHeader(data, offset)
		if err != nil {
			return err
		}

		offset += headerSize

		if offset+size > len(data) {
			return errors.New("packet extends beyond data boundary")
		}

		offset += size
		count++

		if count > 100 { // Sanity check
			return errors.New("too many packets in combined packet")
		}
	}

	return nil
}
