package packets

import (
	"bytes"
	"compress/zlib"
	"encoding/binary"
	"fmt"
	"io"

	"git.sharkk.net/EQ2/Protocol/crypto"
)

// ValidateCRC validates packet CRC using EQ2's custom CRC16
func ValidateCRC(buffer []byte, key uint32) bool {
	if len(buffer) < 2 {
		return false
	}

	// Extract CRC from last 2 bytes (EQ2 uses CRC16)
	packetCRC := binary.BigEndian.Uint16(buffer[len(buffer)-2:])

	// Calculate CRC on data portion (excluding CRC bytes)
	data := buffer[:len(buffer)-2]
	calculatedCRC := crypto.CalculateCRC(data, key)

	return packetCRC == calculatedCRC
}

// AppendCRC appends CRC16 to packet buffer using EQ2's custom CRC
func AppendCRC(buffer []byte, key uint32) []byte {
	crc := crypto.CalculateCRC(buffer, key)
	result := make([]byte, len(buffer)+2)
	copy(result, buffer)
	binary.BigEndian.PutUint16(result[len(buffer):], crc)
	return result
}

// StripCRC removes CRC16 from packet buffer
func StripCRC(buffer []byte) []byte {
	if len(buffer) < 2 {
		return buffer
	}
	return buffer[:len(buffer)-2]
}

// Compress compresses packet data using zlib (matches EQ compression)
func Compress(src []byte) ([]byte, error) {
	if len(src) == 0 {
		return src, nil
	}

	var buf bytes.Buffer

	// Write uncompressed length first (4 bytes) - EQ protocol requirement
	if err := binary.Write(&buf, binary.BigEndian, uint32(len(src))); err != nil {
		return nil, err
	}

	// Compress the data
	w := zlib.NewWriter(&buf)
	if _, err := w.Write(src); err != nil {
		w.Close()
		return nil, err
	}

	if err := w.Close(); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

// Decompress decompresses packet data using zlib
func Decompress(src []byte) ([]byte, error) {
	if len(src) < 4 {
		return nil, fmt.Errorf("compressed data too small")
	}

	// Read uncompressed length (first 4 bytes)
	uncompressedLen := binary.BigEndian.Uint32(src[:4])

	// Sanity check
	if uncompressedLen > MaxPacketSize {
		return nil, fmt.Errorf("uncompressed size %d exceeds max packet size", uncompressedLen)
	}

	// Create reader for compressed data (skip length prefix)
	r, err := zlib.NewReader(bytes.NewReader(src[4:]))
	if err != nil {
		return nil, err
	}
	defer r.Close()

	// Read decompressed data
	decompressed := make([]byte, uncompressedLen)
	if _, err := io.ReadFull(r, decompressed); err != nil {
		return nil, err
	}

	return decompressed, nil
}

// ChatEncode encodes chat data using EQ's XOR-based encoding
func ChatEncode(buffer []byte, encodeKey int) {
	if len(buffer) == 0 || encodeKey == 0 {
		return
	}

	key := byte(encodeKey & 0xFF)
	for i := range buffer {
		buffer[i] ^= key
		// Rotate key for next byte
		key = ((key << 1) | (key >> 7)) & 0xFF
		// Add position-based variation
		if i%3 == 0 {
			key ^= byte(i & 0xFF)
		}
	}
}

// ChatDecode decodes chat data (XOR is symmetric)
func ChatDecode(buffer []byte, decodeKey int) {
	ChatEncode(buffer, decodeKey)
}

// IsChatPacket checks if opcode is a chat-related packet
func IsChatPacket(opcode uint16) bool {
	chatOpcodes := map[uint16]bool{
		0x0300: true, // OP_ChatMsg
		0x0302: true, // OP_TellMsg
		0x0307: true, // OP_ChatLeaveChannelMsg
		0x0308: true, // OP_ChatTellChannelMsg
		0x0309: true, // OP_ChatTellUserMsg
		0x0e07: true, // OP_GuildsayMsg
	}
	return chatOpcodes[opcode]
}

// longToIP converts uint32 IP to string
func longToIP(ip uint32) string {
	return fmt.Sprintf("%d.%d.%d.%d",
		byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}

// IsProtocolPacket checks if buffer contains a valid protocol packet
func IsProtocolPacket(buffer []byte) bool {
	if len(buffer) < 2 {
		return false
	}

	opcode := binary.BigEndian.Uint16(buffer[:2])

	validOpcodes := map[uint16]bool{
		0x0001: true, // OP_SessionRequest
		0x0002: true, // OP_SessionResponse
		0x0003: true, // OP_Combined
		0x0005: true, // OP_SessionDisconnect
		0x0006: true, // OP_KeepAlive
		0x0007: true, // OP_SessionStatRequest
		0x0008: true, // OP_SessionStatResponse
		0x0009: true, // OP_Packet
		0x000d: true, // OP_Fragment
		0x0015: true, // OP_Ack
		0x0019: true, // OP_AppCombined
		0x001d: true, // OP_OutOfOrderAck
		0x001e: true, // OP_OutOfSession
	}

	return validOpcodes[opcode]
}
