package client

import (
	"bytes"
	"encoding/binary"
	"log"

	"go-ts/pkg/protocol"
)

func (c *Client) handlePacket(pkt *protocol.Packet) error {
	// log.Printf("Received Packet: ID=%d, Type=%v, Len=%d", pkt.Header.PacketID, pkt.Header.PacketType(), len(pkt.Data))

	switch pkt.Header.PacketType() {
	case protocol.PacketTypeInit1:
		return c.handleInit(pkt)
	case protocol.PacketTypeCommand:
		// Send ACK
		ackData := make([]byte, 2)
		binary.BigEndian.PutUint16(ackData, pkt.Header.PacketID)

		ack := protocol.NewPacket(protocol.PacketTypeAck, ackData)
		// Spec/ts3j: Header PID for ACK matches the packet being acknowledged
		ack.Header.PacketID = pkt.Header.PacketID
		ack.Header.ClientID = c.ClientID
		// ACKs usually don't have NewProtocol flag set in Header byte
		ack.Header.Type &= ^uint8(protocol.PacketFlagNewProtocol)

		// ACKs for Command packets after handshake must be encrypted
		key := protocol.HandshakeKey
		nonce := protocol.HandshakeNonce

		if c.Handshake != nil && c.Handshake.Step >= 6 && len(c.Handshake.SharedIV) > 0 {
			crypto := &protocol.CryptoState{
				SharedIV:     c.Handshake.SharedIV,
				SharedMac:    c.Handshake.SharedMac,
				GenerationID: 0,
			}
			key, nonce = crypto.GenerateKeyNonce(&ack.Header, true) // Client->Server=true
		}

		// Meta for Client->Server: PID(2) + CID(2) + PT(1) = 5 bytes
		meta := make([]byte, 5)
		binary.BigEndian.PutUint16(meta[0:2], ack.Header.PacketID)
		binary.BigEndian.PutUint16(meta[2:4], ack.Header.ClientID)
		meta[4] = ack.Header.Type

		encData, mac, _ := protocol.EncryptEAX(key, nonce, meta, ack.Data)
		ack.Data = encData
		copy(ack.Header.MAC[:], mac)
		// log.Printf("Sending ACK for server Command PID=%d", pkt.Header.PacketID)

		c.Conn.SendPacket(ack)

		return c.handleCommand(pkt)
	case protocol.PacketTypeVoice:
		c.handleVoice(pkt)
	case protocol.PacketTypePing:
		// Respond with Pong
		pong := protocol.NewPacket(protocol.PacketTypePong, nil)
		// Spec/ts3j: Header PID for Pong matches the Ping ID
		pong.Header.PacketID = pkt.Header.PacketID
		pong.Header.ClientID = c.ClientID
		// Must NOT have NewProtocol (0x20) flag for Pings/Pongs
		pong.Header.Type = uint8(protocol.PacketTypePong) | protocol.PacketFlagUnencrypted

		// Use SharedMac if available, otherwise zeros
		if c.Handshake != nil && len(c.Handshake.SharedMac) > 0 {
			copy(pong.Header.MAC[:], c.Handshake.SharedMac)
		} else {
			for i := 0; i < 8; i++ {
				pong.Header.MAC[i] = 0
			}
		}

		// The body of the Pong must contain the PID of the Ping it's acknowledging
		pong.Data = make([]byte, 2)
		binary.BigEndian.PutUint16(pong.Data, pkt.Header.PacketID)

		log.Printf("Sending Pong (HeaderPID=%d) for Ping", pong.Header.PacketID)
		c.Conn.SendPacket(pong)
	case protocol.PacketTypePong:
		// Server acknowledged our Ping
		log.Printf("Received Pong for sequence %d", pkt.Header.PacketID)
	case protocol.PacketTypeAck:
		// Server acknowledged our packet
		var data []byte
		var err error

		if pkt.Header.FlagUnencrypted() {
			data = pkt.Data
		} else {
			// ACKs are encrypted
			key := protocol.HandshakeKey
			nonce := protocol.HandshakeNonce

			if c.Handshake != nil && c.Handshake.Step >= 6 && len(c.Handshake.SharedIV) > 0 {
				// Use SharedSecret
				crypto := &protocol.CryptoState{
					SharedIV:     c.Handshake.SharedIV,
					SharedMac:    c.Handshake.SharedMac,
					GenerationID: 0,
				}
				key, nonce = crypto.GenerateKeyNonce(&pkt.Header, false) // Server->Client=false
			}

			meta := make([]byte, 3) // Server->Client is 3 bytes
			binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
			meta[2] = pkt.Header.Type

			data, err = protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
			if err != nil {
				// Try fallback to HandshakeKey if SharedSecret failed
				if !bytes.Equal(key, protocol.HandshakeKey[:]) {
					log.Printf("ACK SharedSecret decrypt failed, trying HandshakeKey...")
					key = protocol.HandshakeKey[:]
					nonce = protocol.HandshakeNonce[:]
					data, err = protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
				}

				if err != nil {
					log.Printf("ACK decryption failed (PID=%d): %v", pkt.Header.PacketID, err)
					return nil
				}
			}
		}

		ackPId := uint16(0)
		if len(data) >= 2 {
			ackPId = binary.BigEndian.Uint16(data[0:2])
		}
		log.Printf("Received ACK for PacketID %d (HeaderPID=%d)", ackPId, pkt.Header.PacketID)

		// If ACK is for clientek (PID=1), proceed with clientinit
		if ackPId == 1 && c.Handshake != nil && c.Handshake.Step == 5 {
			log.Println("clientek acknowledged! Sending clientinit...")
			c.Handshake.Step = 6
			return c.sendClientInit()
		}
		// If ACK is for clientinit (PID=2), we're connected!
		if ackPId == 2 && c.Handshake != nil && c.Handshake.Step == 6 {
			log.Println("clientinit acknowledged! Connection established!")
			c.Connected = true
		}
	}
	return nil
}

func (c *Client) handleInit(pkt *protocol.Packet) error {
	// Determine step based on packet content or local state
	// Simple state machine
	if c.Handshake.Step == 0 {
		if err := c.Handshake.HandlePacket1(pkt); err != nil {
			return err
		}
		log.Println("Handshake Step 1 Completed. Sending Step 2...")
		return c.Handshake.SendPacket2()
	} else if c.Handshake.Step == 1 {
		// Wait, step 1 is processed, we sent step 2.
		// We expect Step 3.
		if pkt.Data[0] == 0x03 {
			if err := c.Handshake.HandlePacket3(pkt); err != nil {
				return err
			}
			log.Println("Handshake Step 3 Completed. Sending Step 4 (Puzzle Solution)...")
			if err := c.Handshake.SendPacket4(); err != nil {
				return err
			}
		}
	}
	return nil
}
