package client

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

	"go-ts/pkg/protocol"

	"github.com/dgryski/go-quicklz"
)

// SendCommand sends a command, splitting it into fragments if it exceeds 500 bytes.
func (c *Client) SendCommand(cmd *protocol.Command) error {
	return c.SendCommandString(cmd.Encode())
}

// SendCommandString sends a raw command string with fragmentation.
func (c *Client) SendCommandString(cmdStr string) error {
	data := []byte(cmdStr)
	maxPacketSize := 500
	maxBody := maxPacketSize - 13 // Header is 13 bytes for C->S (MAC 8, PID 2, TYPE 1, CID 2)

	pType := protocol.PacketTypeCommand
	pFlags := uint8(0)

	// ts3j logic: If too large, try compressing
	if len(data)+13 > maxPacketSize {
		compressed := quicklz.Compress(data, 1)
		if len(compressed)+13 < len(data)+13 {
			data = compressed
			pFlags |= protocol.PacketFlagCompressed
			log.Printf("Compressed large command: %d -> %d bytes", len([]byte(cmdStr)), len(data))
		}
	}

	// If still too large (or not compressible), fragment
	if len(data)+13 > maxPacketSize {
		log.Printf("Fragmenting large command (%d bytes) into %d packets", len(data), (len(data)/maxBody)+1)

		for i := 0; i < len(data); i += maxBody {
			end := i + maxBody
			if end > len(data) {
				end = len(data)
			}

			chunk := data[i:end]
			chunkFlags := uint8(0)

			// First packet keeps COMPRESSED flag (if set) and gets FRAGMENTED
			if i == 0 {
				chunkFlags = pFlags | protocol.PacketFlagFragmented
			} else if end == len(data) {
				// Last packet gets FRAGMENTED
				chunkFlags = protocol.PacketFlagFragmented
			} else {
				// Intermediate packets have NO flags (other than NewProtocol added in sendPacketInternal)
				chunkFlags = 0
			}

			if err := c.sendPacketInternal(chunk, pType, chunkFlags); err != nil {
				return err
			}
		}
		return nil
	}

	// Small enough to send in one go
	return c.sendPacketInternal(data, pType, pFlags)
}

// sendPacketInternal handles encryption and low-level header construction for C->S packets.
func (c *Client) sendPacketInternal(data []byte, pType protocol.PacketType, flags uint8) error {
	pkt := protocol.NewPacket(pType, data)
	c.PacketIDCounterC2S++
	pkt.Header.PacketID = c.PacketIDCounterC2S
	pkt.Header.ClientID = c.ClientID
	pkt.Header.Type |= protocol.PacketFlagNewProtocol | flags

	// Encryption
	// Use SharedSecret if Step >= 6, else fallback to HandshakeKey
	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,
		}
		keyArr, nonceArr := crypto.GenerateKeyNonce(&pkt.Header, true) // Client->Server=true
		key = keyArr
		nonce = nonceArr
	}

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

	encData, mac, err := protocol.EncryptEAX(key, nonce, meta, pkt.Data)
	if err != nil {
		return fmt.Errorf("encryption failed: %w", err)
	}

	pkt.Data = encData
	copy(pkt.Header.MAC[:], mac)

	return c.Conn.SendPacket(pkt)
}

func (c *Client) sendClientInit() error {
	// Specialized send for clientinit because it needs PID 2 and uses Map params
	params := map[string]string{
		"client_nickname":         c.Nickname,
		"client_version":          "3.6.2 [Build: 1690976575]",
		"client_platform":         "Windows",
		"client_input_muted":      "0",
		"client_output_muted":     "0",
		"client_outputonly_muted": "0",
		"client_input_hardware":   "1",
		"client_output_hardware":  "1",
		"client_version_sign":     "OyuLO/1bVJtBsXLRWzfGVhNaQd7B9D4QTolZm14DM1uCbSXVvqX3Ssym3sLi/PcvOl+SAUlX6NwBPOsQdwOGDw==",
		"client_key_offset":       fmt.Sprintf("%d", c.Handshake.IdentityOffset),
		"hwid":                    "1234567890",
	}

	var buf bytes.Buffer
	buf.WriteString("clientinit")
	for k, v := range params {
		buf.WriteString(" ")
		buf.WriteString(k)
		if v != "" {
			buf.WriteString("=")
			buf.WriteString(protocol.Escape(v))
		}
	}

	log.Println("Sending clientinit (Packet 2) [Encrypted]...")

	// Reset counter specifically for this sync point
	c.PacketIDCounterC2S = 1 // Next will be 2
	return c.SendCommandString(buf.String())
}
