package main

import (
	"encoding/binary"
	"eq2emu/internal/common/opcodes"
	"eq2emu/internal/packets"
	"eq2emu/internal/udp"
	"fmt"
	"log"
	"time"
)

// LoginRequest represents parsed login request data
type LoginRequest struct {
	Username string
	Password string
	Version  uint16
}

// opcodeToPacketName maps opcodes to packet definition names
var opcodeToPacketName = map[uint16]string{
	opcodes.OpLoginRequestMsg:             "LS_LoginRequest",
	opcodes.OpAllWSDescRequestMsg:         "LS_WorldListRequest",
	opcodes.OpAllCharactersDescRequestMsg: "LS_CharacterListRequest",
	opcodes.OpCreateCharacterRequestMsg:   "LS_CreateCharacterRequest",
	opcodes.OpDeleteCharacterRequestMsg:   "LS_DeleteCharacterRequest",
	opcodes.OpPlayCharacterRequestMsg:     "LS_PlayCharacterRequest",
}

// parsePacketWithDefinition parses packet using definitions or fails
func (lc *LoginClient) parsePacketWithDefinition(opcode uint16, data []byte) (map[string]any, error) {
	packetName, exists := opcodeToPacketName[opcode]
	if !exists {
		return nil, fmt.Errorf("no packet name mapping for opcode 0x%04X", opcode)
	}

	packetDef, exists := packets.GetPacket(packetName)
	if !exists {
		return nil, fmt.Errorf("no packet definition found for %s", packetName)
	}

	// Use client version for parsing, default flags
	result, err := packetDef.Parse(data, uint32(lc.version), 0)
	if err != nil {
		return nil, fmt.Errorf("packet parsing failed for %s: %w", packetName, err)
	}

	return result, nil
}

// parseLoginRequest parses the login request packet data
func (lc *LoginClient) parseLoginRequest(data []byte) (*LoginRequest, error) {
	parsed, err := lc.parsePacketWithDefinition(opcodes.OpLoginRequestMsg, data)
	if err != nil {
		return nil, err
	}

	req := &LoginRequest{}

	if username, ok := parsed["username"].(string); ok {
		req.Username = username
	}
	if password, ok := parsed["password"].(string); ok {
		req.Password = password
	}
	if version, ok := parsed["version"].(uint16); ok {
		req.Version = version
	}

	return req, nil
}

// sendLoginDenied sends login failure response
func (lc *LoginClient) sendLoginDenied() {
	data := make([]byte, 12)
	data[0] = 1 // reply_code: Invalid username or password
	binary.LittleEndian.PutUint32(data[4:], 0xFFFFFFFF)
	binary.LittleEndian.PutUint32(data[8:], 0xFFFFFFFF)

	packet := &udp.ApplicationPacket{
		Opcode: opcodes.OpLoginReplyMsg,
		Data:   data,
	}
	lc.connection.SendPacket(packet)

	// Disconnect after short delay
	time.AfterFunc(1*time.Second, func() {
		lc.Disconnect()
	})
}

// sendLoginDeniedBadVersion sends bad version response
func (lc *LoginClient) sendLoginDeniedBadVersion() {
	data := make([]byte, 12)
	data[0] = 6 // reply_code: Version mismatch
	binary.LittleEndian.PutUint32(data[4:], 0xFFFFFFFF)
	binary.LittleEndian.PutUint32(data[8:], 0xFFFFFFFF)

	packet := &udp.ApplicationPacket{
		Opcode: opcodes.OpLoginReplyMsg,
		Data:   data,
	}
	lc.connection.SendPacket(packet)

	time.AfterFunc(1*time.Second, func() {
		lc.Disconnect()
	})
}

// sendLoginAccepted sends successful login response
func (lc *LoginClient) sendLoginAccepted() {
	config := lc.server.GetConfig()

	// Build login response packet
	data := make([]byte, 64) // Base size, will expand as needed
	offset := 0

	// Account ID
	binary.LittleEndian.PutUint32(data[offset:], uint32(lc.account.ID))
	offset += 4

	// Login response code (0 = success)
	data[offset] = 0
	offset++

	// Do not force SOGA flag
	data[offset] = 1
	offset++

	// Subscription level
	binary.LittleEndian.PutUint32(data[offset:], config.DefaultSubscriptionLevel)
	offset += 4

	// Race flags (enabled races)
	binary.LittleEndian.PutUint32(data[offset:], 0x1FFFFF)
	offset += 4

	// Class flags (enabled classes)
	binary.LittleEndian.PutUint32(data[offset:], 0x7FFFFFE)
	offset += 4

	// Username (16-bit string)
	username := lc.account.Username
	binary.LittleEndian.PutUint16(data[offset:], uint16(len(username)))
	offset += 2
	copy(data[offset:], username)
	offset += len(username)

	// Expansion flags
	binary.LittleEndian.PutUint16(data[offset:], config.ExpansionFlag)
	offset += 2

	// Additional flags
	data[offset] = 0xFF
	data[offset+1] = 0xFF
	data[offset+2] = 0xFF
	offset += 3

	// Class access flag
	data[offset] = 0xFF
	offset++

	// Enabled races
	binary.LittleEndian.PutUint32(data[offset:], config.EnabledRaces)
	offset += 4

	// Cities flag
	data[offset] = config.CitiesFlag
	offset++

	packet := &udp.ApplicationPacket{
		Opcode: opcodes.OpLoginReplyMsg,
		Data:   data[:offset],
	}
	lc.connection.SendPacket(packet)
}

// sendWorldList sends available world servers to client
func (lc *LoginClient) sendWorldList() {
	worlds := lc.server.worldList.GetActiveWorlds()

	// Build world list packet
	data := make([]byte, 0, 1024)

	// Number of worlds
	worldCount := uint8(len(worlds))
	data = append(data, worldCount)

	for _, world := range worlds {
		// World ID
		worldID := make([]byte, 4)
		binary.LittleEndian.PutUint32(worldID, uint32(world.ID))
		data = append(data, worldID...)

		// World name (16-bit string)
		nameLen := make([]byte, 2)
		binary.LittleEndian.PutUint16(nameLen, uint16(len(world.Name)))
		data = append(data, nameLen...)
		data = append(data, []byte(world.Name)...)

		// World status flags
		var flags uint8
		if world.Online {
			flags |= 0x01
		}
		if world.Locked {
			flags |= 0x02
		}
		if world.Hidden {
			flags |= 0x04
		}
		data = append(data, flags)

		// Population (0-3, where 3 = full)
		data = append(data, world.PopulationLevel)
	}

	packet := &udp.ApplicationPacket{
		Opcode: opcodes.OpWorldListMsg,
		Data:   data,
	}
	lc.connection.SendPacket(packet)
}

// sendCharacterList sends character list to client
func (lc *LoginClient) sendCharacterList() {
	if lc.account == nil {
		return
	}

	data := make([]byte, 0, 2048)

	// Number of characters
	charCount := uint8(len(lc.account.Characters))
	data = append(data, charCount)

	// Character data
	for _, char := range lc.account.Characters {
		if char.Deleted {
			continue
		}

		// Character ID
		charID := make([]byte, 4)
		binary.LittleEndian.PutUint32(charID, uint32(char.ID))
		data = append(data, charID...)

		// Server ID
		serverID := make([]byte, 4)
		binary.LittleEndian.PutUint32(serverID, uint32(char.ServerID))
		data = append(data, serverID...)

		// Character name (16-bit string)
		nameLen := make([]byte, 2)
		binary.LittleEndian.PutUint16(nameLen, uint16(len(char.Name)))
		data = append(data, nameLen...)
		data = append(data, []byte(char.Name)...)

		// Character stats
		data = append(data, byte(char.Race))
		data = append(data, byte(char.Gender))
		data = append(data, byte(char.Class))
		data = append(data, byte(char.Level))

		// Creation timestamp
		timestamp := make([]byte, 4)
		binary.LittleEndian.PutUint32(timestamp, uint32(char.CreatedDate.Unix()))
		data = append(data, timestamp...)
	}

	// Account info
	accountID := make([]byte, 4)
	binary.LittleEndian.PutUint32(accountID, uint32(lc.account.ID))
	data = append(data, accountID...)

	// Max characters
	data = append(data, 0xFF, 0xFF, 0xFF, 0xFF) // unknown1
	data = append(data, 0x00, 0x00)             // unknown2
	data = append(data, 0x07, 0x00, 0x00, 0x00) // max chars (7)
	data = append(data, 0x00)                   // unknown4

	packet := &udp.ApplicationPacket{
		Opcode: opcodes.OpAllCharactersDescReplyMsg,
		Data:   data,
	}
	lc.connection.SendPacket(packet)
}

// handleCharacterCreateRequest processes character creation
func (lc *LoginClient) handleCharacterCreateRequest(packet *udp.ApplicationPacket) {
	if !lc.authenticated {
		lc.Disconnect()
		return
	}

	parsed, err := lc.parsePacketWithDefinition(opcodes.OpCreateCharacterRequestMsg, packet.Data)
	if err != nil {
		log.Printf("Failed to parse character create request: %v", err)
		lc.sendCharacterCreateFailed(1)
		return
	}

	serverID, ok := parsed["server_id"].(uint32)
	if !ok {
		lc.sendCharacterCreateFailed(1)
		return
	}

	name, ok := parsed["character_name"].(string)
	if !ok {
		lc.sendCharacterCreateFailed(1)
		return
	}

	// Validate character name
	if len(name) < 3 || len(name) > 20 {
		lc.sendCharacterCreateFailed(9) // Bad name length
		return
	}

	// Check if name is taken
	exists, err := lc.server.database.CharacterNameExists(name, int32(serverID))
	if err != nil {
		log.Printf("Error checking character name: %v", err)
		lc.sendCharacterCreateFailed(1)
		return
	}

	if exists {
		lc.sendCharacterCreateFailed(12) // Name taken
		return
	}

	// Create character in database
	char := &Character{
		AccountID:   lc.account.ID,
		ServerID:    int32(serverID),
		Name:        name,
		Level:       1,
		Race:        1, // Would be parsed from packet
		Gender:      1, // Would be parsed from packet
		Class:       1, // Would be parsed from packet
		CreatedDate: time.Now(),
	}

	charID, err := lc.server.database.CreateCharacter(char)
	if err != nil {
		log.Printf("Error creating character: %v", err)
		lc.sendCharacterCreateFailed(1)
		return
	}

	char.ID = charID
	lc.account.Characters = append(lc.account.Characters, char)

	lc.sendCharacterCreateSuccess(char)
	lc.sendCharacterList() // Refresh character list
}

// sendCharacterCreateSuccess sends successful character creation response
func (lc *LoginClient) sendCharacterCreateSuccess(char *Character) {
	data := make([]byte, 64)
	offset := 0

	// Account ID
	binary.LittleEndian.PutUint32(data[offset:], uint32(lc.account.ID))
	offset += 4

	// Response code (0 = success)
	binary.LittleEndian.PutUint32(data[offset:], 0)
	offset += 4

	// Character name
	nameLen := uint16(len(char.Name))
	binary.LittleEndian.PutUint16(data[offset:], nameLen)
	offset += 2
	copy(data[offset:], char.Name)
	offset += int(nameLen)

	packet := &udp.ApplicationPacket{
		Opcode: opcodes.OpCreateCharacterReplyMsg,
		Data:   data[:offset],
	}
	lc.connection.SendPacket(packet)
}

// sendCharacterCreateFailed sends character creation failure response
func (lc *LoginClient) sendCharacterCreateFailed(reason uint8) {
	data := make([]byte, 16)

	binary.LittleEndian.PutUint32(data[0:], uint32(lc.account.ID))
	data[4] = reason

	packet := &udp.ApplicationPacket{
		Opcode: opcodes.OpCreateCharacterReplyMsg,
		Data:   data,
	}
	lc.connection.SendPacket(packet)
}

// handleCharacterDeleteRequest processes character deletion
func (lc *LoginClient) handleCharacterDeleteRequest(packet *udp.ApplicationPacket) {
	if !lc.authenticated {
		lc.Disconnect()
		return
	}

	parsed, err := lc.parsePacketWithDefinition(opcodes.OpDeleteCharacterRequestMsg, packet.Data)
	if err != nil {
		log.Printf("Failed to parse character delete request: %v", err)
		return
	}

	charID, ok := parsed["character_id"].(uint32)
	if !ok {
		return
	}

	serverID, ok := parsed["server_id"].(uint32)
	if !ok {
		return
	}

	// Verify character belongs to this account
	var char *Character
	for _, c := range lc.account.Characters {
		if c.ID == int32(charID) && c.ServerID == int32(serverID) {
			char = c
			break
		}
	}

	if char == nil {
		log.Printf("Account %d attempted to delete character %d that doesn't belong to them", lc.account.ID, charID)
		return
	}

	// Mark character as deleted
	err = lc.server.database.DeleteCharacter(int32(charID), lc.account.ID)
	if err != nil {
		log.Printf("Error deleting character: %v", err)
		return
	}

	char.Deleted = true

	// Send deletion response
	data := make([]byte, 24)
	data[0] = 1 // Success
	binary.LittleEndian.PutUint32(data[4:], serverID)
	binary.LittleEndian.PutUint32(data[8:], charID)
	binary.LittleEndian.PutUint32(data[12:], uint32(lc.account.ID))

	responsePacket := &udp.ApplicationPacket{
		Opcode: opcodes.OpDeleteCharacterReplyMsg,
		Data:   data,
	}
	lc.connection.SendPacket(responsePacket)

	lc.sendCharacterList() // Refresh character list
}

// handlePlayCharacterRequest processes character selection for gameplay
func (lc *LoginClient) handlePlayCharacterRequest(packet *udp.ApplicationPacket) {
	if !lc.authenticated {
		lc.Disconnect()
		return
	}

	parsed, err := lc.parsePacketWithDefinition(opcodes.OpPlayCharacterRequestMsg, packet.Data)
	if err != nil {
		log.Printf("Failed to parse play character request: %v", err)
		lc.sendPlayFailed(1)
		return
	}

	charID, ok := parsed["character_id"].(uint32)
	if !ok {
		lc.sendPlayFailed(1)
		return
	}

	serverID, ok := parsed["server_id"].(uint32)
	if !ok {
		lc.sendPlayFailed(1)
		return
	}

	// Find world server
	world := lc.server.worldList.GetWorld(int32(serverID))
	if world == nil || !world.Online {
		lc.sendPlayFailed(2) // Server unavailable
		return
	}

	// Verify character ownership
	var char *Character
	for _, c := range lc.account.Characters {
		if c.ID == int32(charID) && c.ServerID == int32(serverID) {
			char = c
			break
		}
	}

	if char == nil {
		lc.sendPlayFailed(1) // Character not found
		return
	}

	lc.pendingPlayCharID = int32(charID)

	// Send play request to world server
	err = lc.server.worldList.SendPlayRequest(world, lc.account.ID, int32(charID))
	if err != nil {
		log.Printf("Error sending play request to world server: %v", err)
		lc.sendPlayFailed(1)
		return
	}

	// World server will respond with connection details
	log.Printf("Account %s requesting to play character %s on server %s",
		lc.account.Username, char.Name, world.Name)
}

// sendPlayFailed sends play character failure response
func (lc *LoginClient) sendPlayFailed(reason uint8) {
	data := make([]byte, 16)
	data[0] = reason
	binary.LittleEndian.PutUint32(data[4:], uint32(lc.account.ID))

	packet := &udp.ApplicationPacket{
		Opcode: opcodes.OpPlayCharacterReplyMsg,
		Data:   data,
	}
	lc.connection.SendPacket(packet)
}
