package main

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

// LoginClient represents a connected client session
type LoginClient struct {
	connection    *udp.Connection
	server        *LoginServer
	account       *Account
	lastActivity  time.Time
	authenticated bool
	version       uint16
	sessionID     string

	// Client state
	needsWorldList    bool
	sentCharacterList bool
	pendingPlayCharID int32
	createRequest     *CharacterCreateRequest
}

// Account represents an authenticated user account
type Account struct {
	ID            int32
	Username      string
	LSAdmin       bool
	WorldAdmin    bool
	Characters    []*Character
	LastLogin     time.Time
	IPAddress     string
	ClientVersion uint16
}

// Character represents a character in the database
type Character struct {
	ID          int32
	AccountID   int32
	ServerID    int32
	Name        string
	Level       int8
	Race        int8
	Class       int8
	Gender      int8
	CreatedDate time.Time
	Deleted     bool
}

// CharacterCreateRequest holds pending character creation data
type CharacterCreateRequest struct {
	ServerID  int32
	Name      string
	Race      int8
	Gender    int8
	Class     int8
	Face      int8
	Hair      int8
	HairColor int8
	SkinColor int8
	EyeColor  int8
	Timestamp time.Time
}

// NewLoginClient creates a new login client instance
func NewLoginClient(conn *udp.Connection, server *LoginServer) *LoginClient {
	return &LoginClient{
		connection:        conn,
		server:            server,
		lastActivity:      time.Now(),
		sessionID:         fmt.Sprintf("%d", conn.GetSessionID()),
		needsWorldList:    true,
		sentCharacterList: false,
	}
}

// ProcessPacket handles incoming packets from the client
func (lc *LoginClient) ProcessPacket(packet *udp.ApplicationPacket) {
	lc.lastActivity = time.Now()

	switch packet.Opcode {
	case opcodes.OpLoginRequestMsg:
		lc.handleLoginRequest(packet)
	case opcodes.OpAllWSDescRequestMsg:
		lc.handleWorldListRequest(packet)
	case opcodes.OpAllCharactersDescRequestMsg:
		lc.handleCharacterListRequest(packet)
	case opcodes.OpCreateCharacterRequestMsg:
		lc.handleCharacterCreateRequest(packet)
	case opcodes.OpDeleteCharacterRequestMsg:
		lc.handleCharacterDeleteRequest(packet)
	case opcodes.OpPlayCharacterRequestMsg:
		lc.handlePlayCharacterRequest(packet)
	case opcodes.OpKeymapLoadMsg:
		// Client keymap request - usually ignored
		break
	default:
		log.Printf("Unknown packet opcode from client %s: 0x%04X", lc.sessionID, packet.Opcode)
	}
}

// handleLoginRequest processes client login attempts
func (lc *LoginClient) handleLoginRequest(packet *udp.ApplicationPacket) {
	lc.server.IncrementLoginAttempts()

	// Parse login request packet
	loginReq, err := lc.parseLoginRequest(packet.Data)
	if err != nil {
		log.Printf("Failed to parse login request from %s: %v", lc.sessionID, err)
		lc.sendLoginDenied()
		return
	}

	lc.version = loginReq.Version

	// Check if client version is supported
	if !lc.server.GetConfig().IsVersionSupported(lc.version) {
		log.Printf("Unsupported client version %d from %s", lc.version, lc.sessionID)
		lc.sendLoginDeniedBadVersion()
		return
	}

	// Authenticate with database
	account, err := lc.server.database.AuthenticateAccount(loginReq.Username, loginReq.Password)
	if err != nil {
		log.Printf("Authentication failed for %s: %v", loginReq.Username, err)
		lc.sendLoginDenied()
		return
	}

	if account == nil {
		log.Printf("Invalid credentials for %s", loginReq.Username)
		lc.sendLoginDenied()
		return
	}

	// Check for existing session
	lc.server.clientMutex.RLock()
	for _, existingClient := range lc.server.clients {
		if existingClient.account != nil && existingClient.account.ID == account.ID && existingClient != lc {
			log.Printf("Account %s already logged in, disconnecting previous session", account.Username)
			existingClient.Disconnect()
			break
		}
	}
	lc.server.clientMutex.RUnlock()

	// Update account info
	account.LastLogin = time.Now()
	account.IPAddress = lc.connection.GetClientAddr().IP.String()
	account.ClientVersion = lc.version
	lc.server.database.UpdateAccountLogin(account)

	lc.account = account
	lc.authenticated = true
	lc.server.IncrementSuccessfulLogins()

	log.Printf("User %s successfully authenticated", account.Username)
	lc.sendLoginAccepted()
}

// handleWorldListRequest sends the list of available world servers
func (lc *LoginClient) handleWorldListRequest(packet *udp.ApplicationPacket) {
	if !lc.authenticated {
		lc.Disconnect()
		return
	}

	lc.sendWorldList()
	lc.needsWorldList = false

	// Load and send character list
	if !lc.sentCharacterList {
		characters, err := lc.server.database.LoadCharacters(lc.account.ID, lc.version)
		if err != nil {
			log.Printf("Failed to load characters for account %d: %v", lc.account.ID, err)
		} else {
			lc.account.Characters = characters
			lc.sentCharacterList = true
		}
		lc.sendCharacterList()
	}
}

// handleCharacterListRequest handles explicit character list requests
func (lc *LoginClient) handleCharacterListRequest(packet *udp.ApplicationPacket) {
	if !lc.authenticated {
		lc.Disconnect()
		return
	}

	lc.sendCharacterList()
}

// IsStale returns true if the client connection should be cleaned up
func (lc *LoginClient) IsStale() bool {
	return time.Since(lc.lastActivity) > 5*time.Minute
}

// Disconnect closes the client connection and cleans up
func (lc *LoginClient) Disconnect() {
	if lc.connection != nil {
		lc.connection.Close()
	}

	// Clean up any pending requests
	lc.createRequest = nil

	log.Printf("Client %s disconnected", lc.sessionID)
	lc.server.RemoveClient(lc.sessionID)
}

// GetAccount returns the authenticated account
func (lc *LoginClient) GetAccount() *Account {
	return lc.account
}

// GetVersion returns the client version
func (lc *LoginClient) GetVersion() uint16 {
	return lc.version
}

// GetSessionID returns the session identifier
func (lc *LoginClient) GetSessionID() string {
	return lc.sessionID
}
