package packets

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

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

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

	// Session packets are not CRC protected
	if len(buffer) >= 2 && buffer[0] == 0x00 &&
		(buffer[1] == byte(opcodes.OP_SessionRequest) ||
			buffer[1] == byte(opcodes.OP_SessionResponse) ||
			buffer[1] == byte(opcodes.OP_OutOfSession)) {
		return true
	}

	// Combined application packets are also exempt (OP_AppCombined = 0x0019)
	if len(buffer) >= 4 && buffer[2] == 0x00 && buffer[3] == 0x19 {
		return true
	}

	// All other packets must have valid CRC
	// Extract CRC from last 2 bytes (network byte order)
	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)

	// Valid if no CRC present (packetCRC == 0) or CRCs match
	return packetCRC == 0 || calculatedCRC == packetCRC
}

// 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 or simple encoding (matches C++ EQProtocolPacket::Compress)
// Uses zlib for packets > 30 bytes, simple encoding for smaller packets
func Compress(src []byte) ([]byte, error) {
	if len(src) == 0 {
		return src, nil
	}

	// C++ uses 30 bytes as threshold between simple encoding and zlib
	if len(src) > 30 {
		// Use zlib compression for larger packets
		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
	}

	// Use simple encoding for smaller packets (just return data as-is)
	// The 0xa5 flag is added by the caller, not here
	return src, nil
}

// Decompress decompresses packet data using zlib
// This is called after the compression flag has already been checked and removed
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
}

// CompressPacket adds compression with proper flag handling (matches C++ EQProtocolPacket::Compress)
// Uses zlib (0x5a) for packets > 30 bytes, simple encoding (0xa5) for smaller packets
func CompressPacket(buffer []byte) ([]byte, error) {
	if len(buffer) < 2 {
		return buffer, nil
	}

	// Determine flag offset based on opcode size
	flagOffset := 1
	if buffer[0] == 0x00 {
		flagOffset = 2 // Two-byte opcode
	}

	if len(buffer) <= flagOffset {
		return buffer, nil // Too small to compress
	}

	// Get data to compress (after opcode)
	dataToCompress := buffer[flagOffset:]
	
	// Choose compression method based on size (C++ uses 30 byte threshold)
	if len(dataToCompress) > 30 {
		// Use zlib compression
		compressed, err := Compress(dataToCompress)
		if err != nil {
			return nil, err
		}

		// Only use if compression actually reduced size
		if len(compressed) < len(dataToCompress) {
			result := make([]byte, flagOffset+1+len(compressed))
			copy(result[:flagOffset], buffer[:flagOffset]) // Copy opcode
			result[flagOffset] = 0x5a                       // Zlib flag
			copy(result[flagOffset+1:], compressed)         // Compressed data
			return result, nil
		}
	} else {
		// Use simple encoding - just add flag
		result := make([]byte, len(buffer)+1)
		copy(result[:flagOffset], buffer[:flagOffset]) // Copy opcode
		result[flagOffset] = 0xa5                       // Simple encoding flag
		copy(result[flagOffset+1:], dataToCompress)     // Original data
		return result, nil
	}

	// No compression if it doesn't help
	return buffer, nil
}

// DecompressPacket handles decompression with flag checking (matches C++ EQProtocolPacket::Decompress)
// Supports both zlib compression (0x5a) and simple encoding (0xa5)
func DecompressPacket(buffer []byte) ([]byte, error) {
	if len(buffer) < 3 {
		return buffer, nil // Too small to be compressed
	}

	// Determine flag offset based on opcode size
	flagOffset := uint32(1)
	if buffer[0] == 0x00 {
		flagOffset = 2 // Two-byte opcode
	}

	if uint32(len(buffer)) <= flagOffset {
		return buffer, nil // No room for compression flag
	}

	compressionFlag := buffer[flagOffset]

	// Check compression type
	if compressionFlag == 0x5a {
		// Zlib compression
		// Decompress data after flag, excluding last 2 CRC bytes
		dataStart := flagOffset + 1
		dataEnd := uint32(len(buffer)) - 2
		if dataEnd <= dataStart {
			return nil, fmt.Errorf("invalid compressed packet size")
		}

		decompressed, err := Decompress(buffer[dataStart:dataEnd])
		if err != nil {
			return nil, fmt.Errorf("zlib decompression failed: %w", err)
		}

		// Rebuild packet: opcode + decompressed data + CRC
		result := make([]byte, flagOffset+uint32(len(decompressed))+2)
		copy(result[:flagOffset], buffer[:flagOffset]) // Copy opcode
		copy(result[flagOffset:], decompressed)         // Copy decompressed data
		// Copy CRC bytes
		result[len(result)-2] = buffer[len(buffer)-2]
		result[len(result)-1] = buffer[len(buffer)-1]

		return result, nil
	} else if compressionFlag == 0xa5 {
		// Simple encoding - just remove the encoding flag
		result := make([]byte, len(buffer)-1)
		copy(result[:flagOffset], buffer[:flagOffset])                    // Copy opcode
		copy(result[flagOffset:], buffer[flagOffset+1:])                   // Skip flag, copy rest
		return result, nil
	}

	// No compression
	return buffer, nil
}

// ChatEncode encodes chat data using EQ's XOR-based encoding (matches C++ EQProtocolPacket::ChatEncode)
// Uses 4-byte block XOR with rolling key that updates from encrypted data
func ChatEncode(buffer []byte, encodeKey int) {
	if len(buffer) <= 2 || encodeKey == 0 {
		return
	}

	// Skip encoding for certain packet types (matches C++ conditions)
	if buffer[1] == 0x01 || buffer[0] == 0x02 || buffer[0] == 0x1d {
		return
	}

	// Skip first 2 bytes (opcode)
	data := buffer[2:]
	size := len(data)
	key := int32(encodeKey)

	// Encode 4-byte blocks with rolling key
	i := 0
	for ; i+4 <= size; i += 4 {
		// Read 4 bytes as int32 (little-endian like C++)
		pt := binary.LittleEndian.Uint32(data[i:i+4])
		encrypted := pt ^ uint32(key)
		key = int32(encrypted) // Update key with encrypted data
		binary.LittleEndian.PutUint32(data[i:i+4], encrypted)
	}

	// Encode remaining bytes with last key byte
	keyByte := byte(key & 0xFF)
	for ; i < size; i++ {
		data[i] ^= keyByte
	}
}

// ChatDecode decodes chat data using EQ's XOR-based encoding (matches C++ EQProtocolPacket::ChatDecode)
// Uses 4-byte block XOR with rolling key that updates from encrypted data
func ChatDecode(buffer []byte, decodeKey int) {
	if len(buffer) <= 2 || decodeKey == 0 {
		return
	}

	// Skip decoding for certain packet types (matches C++ conditions)
	if buffer[1] == 0x01 || buffer[0] == 0x02 || buffer[0] == 0x1d {
		return
	}

	// Skip first 2 bytes (opcode)
	data := buffer[2:]
	size := len(data)
	key := int32(decodeKey)

	// Decode 4-byte blocks with rolling key
	i := 0
	for ; i+4 <= size; i += 4 {
		// Read 4 bytes as int32 (little-endian like C++)
		encrypted := binary.LittleEndian.Uint32(data[i:i+4])
		decrypted := encrypted ^ uint32(key)
		key = int32(encrypted) // Update key with encrypted data (before decryption)
		binary.LittleEndian.PutUint32(data[i:i+4], decrypted)
	}

	// Decode remaining bytes with last key byte
	keyByte := byte(key & 0xFF)
	for ; i < size; i++ {
		data[i] ^= keyByte
	}
}

// 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 (matches C++ EQProtocolPacket::IsProtocolPacket)
func IsProtocolPacket(buffer []byte) bool {
	if len(buffer) < 2 {
		return false
	}

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

	// Check against known protocol opcodes
	switch opcode {
	case opcodes.OP_SessionRequest,
		opcodes.OP_SessionResponse,
		opcodes.OP_Combined,
		opcodes.OP_SessionDisconnect,
		opcodes.OP_KeepAlive,
		opcodes.OP_SessionStatRequest,
		opcodes.OP_SessionStatResponse,
		opcodes.OP_Packet,
		opcodes.OP_Fragment,
		opcodes.OP_Ack,
		opcodes.OP_AppCombined,
		opcodes.OP_OutOfOrderAck,
		opcodes.OP_OutOfSession:
		return true
	default:
		return false
	}
}
