package packets

import (
	"encoding/binary"

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

// DefaultOpcodeSize is the default opcode size for application packets
var DefaultOpcodeSize uint8 = 2

// AppPacket handles high-level game opcodes (matches EQApplicationPacket)
type AppPacket struct {
	*Packet

	emuOpcode  opcodes.EmuOpcode // Cached emulator opcode
	opcodeSize uint8             // Size of opcode in bytes (1 or 2)
	manager    opcodes.Manager   // Opcode manager for translation
}

// NewAppPacket creates a new application packet
func NewAppPacket(manager opcodes.Manager) *AppPacket {
	return &AppPacket{
		Packet:     NewPacket(0, nil),
		emuOpcode:  opcodes.OP_Unknown,
		opcodeSize: DefaultOpcodeSize,
		manager:    manager,
	}
}

// NewAppPacketWithOp creates with opcode
func NewAppPacketWithOp(op opcodes.EmuOpcode, manager opcodes.Manager) *AppPacket {
	app := &AppPacket{
		Packet:     NewPacket(0, nil),
		opcodeSize: DefaultOpcodeSize,
		manager:    manager,
	}
	app.SetOpcode(op)
	return app
}

// NewAppPacketWithSize creates with opcode and size
func NewAppPacketWithSize(op opcodes.EmuOpcode, size uint32, manager opcodes.Manager) *AppPacket {
	app := &AppPacket{
		Packet:     NewPacket(0, make([]byte, size)),
		opcodeSize: DefaultOpcodeSize,
		manager:    manager,
	}
	app.SetOpcode(op)
	return app
}

// NewAppPacketWithData creates with opcode and data
func NewAppPacketWithData(op opcodes.EmuOpcode, data []byte, manager opcodes.Manager) *AppPacket {
	app := &AppPacket{
		Packet:     NewPacket(0, data),
		opcodeSize: DefaultOpcodeSize,
		manager:    manager,
	}
	app.SetOpcode(op)
	return app
}

// NewAppPacketFromRaw creates from raw buffer (matches EQApplicationPacket constructor)
func NewAppPacketFromRaw(buf []byte, opcodeSize uint8, manager opcodes.Manager) *AppPacket {
	if opcodeSize == 0 {
		opcodeSize = DefaultOpcodeSize
	}

	app := &AppPacket{
		Packet:     NewPacket(0, nil),
		opcodeSize: opcodeSize,
		manager:    manager,
	}

	offset := 0

	// Extract opcode based on size
	if opcodeSize == 1 && len(buf) >= 1 {
		app.Opcode = uint16(buf[0])
		offset = 1
	} else if len(buf) >= 2 {
		app.Opcode = binary.BigEndian.Uint16(buf[:2])
		offset = 2
	}

	// Copy remaining data as payload
	if len(buf) > offset {
		app.Buffer = make([]byte, len(buf)-offset)
		copy(app.Buffer, buf[offset:])
	}

	// Convert EQ opcode to emulator opcode
	if app.manager != nil {
		app.emuOpcode = app.manager.EQToEmu(app.Opcode)
	} else {
		app.emuOpcode = opcodes.EmuOpcode(app.Opcode)
	}

	return app
}

// Size returns total packet size including opcode
func (a *AppPacket) Size() uint32 {
	// Handle special encoding where low byte = 0x00 needs extra byte
	extraBytes := uint32(0)
	if a.opcodeSize == 2 && (a.Opcode&0x00ff) == 0 {
		extraBytes = 1
	}
	return uint32(len(a.Buffer)) + uint32(a.opcodeSize) + extraBytes
}

// SetOpcode sets the emulator opcode and translates to EQ opcode
func (a *AppPacket) SetOpcode(op opcodes.EmuOpcode) {
	a.emuOpcode = op
	if a.manager != nil {
		a.Opcode = a.manager.EmuToEQ(op)
	} else {
		a.Opcode = uint16(op)
	}
}

// GetOpcode returns the emulator opcode
func (a *AppPacket) GetOpcode() opcodes.EmuOpcode {
	if a.emuOpcode == opcodes.OP_Unknown && a.manager != nil {
		a.emuOpcode = a.manager.EQToEmu(a.Opcode)
	}
	return a.emuOpcode
}

// GetOpcodeName returns the name of the current opcode
func (a *AppPacket) GetOpcodeName() string {
	if a.manager != nil {
		return a.manager.EmuToName(a.GetOpcode())
	}
	return "OP_Unknown"
}

// SetManager sets the opcode manager
func (a *AppPacket) SetManager(manager opcodes.Manager) {
	a.manager = manager
	// Re-translate if we have an opcode
	if a.emuOpcode != opcodes.OP_Unknown && a.manager != nil {
		a.Opcode = a.manager.EmuToEQ(a.emuOpcode)
	}
}

// Serialize writes to destination (matches EQApplicationPacket::serialize)
func (a *AppPacket) Serialize(dest []byte) uint32 {
	opcodeBytes := a.opcodeSize
	pos := 0

	if a.opcodeSize == 1 {
		// Single-byte opcode
		dest[pos] = byte(a.Opcode)
		pos++
	} else {
		// Two-byte opcode with special encoding rules
		// Application opcodes with low byte = 0x00 need extra 0x00 prefix
		if (a.Opcode & 0x00ff) == 0 {
			dest[pos] = 0
			pos++
			binary.BigEndian.PutUint16(dest[pos:], a.Opcode)
			pos += 2
			opcodeBytes++
		} else {
			binary.BigEndian.PutUint16(dest[pos:], a.Opcode)
			pos += 2
		}
	}

	// Copy packet data after opcode
	copy(dest[pos:], a.Buffer)

	return uint32(len(a.Buffer)) + uint32(opcodeBytes)
}

// Combine combines with another app packet (matches EQApplicationPacket::combine)
func (a *AppPacket) Combine(rhs *AppPacket) bool {
	// Application packet combining is not implemented in original
	// Use protocol-level combining instead
	return false
}

// Copy creates a deep copy
func (a *AppPacket) Copy() *AppPacket {
	newApp := &AppPacket{
		Packet:     NewPacket(a.Opcode, a.Buffer),
		emuOpcode:  a.emuOpcode,
		opcodeSize: a.opcodeSize,
		manager:    a.manager,
	}
	newApp.Packet.CopyInfo(a.Packet)
	return newApp
}

// ToProto converts to protocol packet
func (a *AppPacket) ToProto() *ProtoPacket {
	// Serialize the application packet
	tmpBuf := make([]byte, a.Size())
	a.Serialize(tmpBuf)

	proto := NewProtoPacket(opcodes.OP_Packet, tmpBuf, a.manager)
	proto.LoginOp = a.emuOpcode
	proto.CopyInfo(a.Packet)

	return proto
}
