package client

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

	"go-ts/pkg/protocol"

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

func (c *Client) handleCommand(pkt *protocol.Packet) error {
	// Check if Encrypted
	// PacketTypeCommand is usually encrypted.
	// Flag check? The flag is in the Header (e.g. Unencrypted flag).
	// If Unencrypted flag is SET, it's cleartext.
	// Spec: "Command ... Encrypted: ✓". So Unencrypted flag is CLEARED.

	// Decrypt if necessary
	var data []byte
	var err error

	if pkt.Header.FlagUnencrypted() {
		data = pkt.Data
	} else {
		var key, nonce []byte
		decrypted := false

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

			// AAD for Server->Client: PacketID (2) + Type|Flags (1)
			meta := make([]byte, 3)
			binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
			meta[2] = pkt.Header.Type // Type includes Flags

			data, err = protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
			if err == nil {
				decrypted = true
			} else {
				log.Printf("SharedSecret decrypt failed (PID=%d): %v. Trying HandshakeKey...", pkt.Header.PacketID, err)
			}
		}

		// 2. Fallback to HandshakeKey
		if !decrypted {
			key = protocol.HandshakeKey[:]
			nonce = protocol.HandshakeNonce[:]

			// AAD matching KeyNonce derivation context?
			// HandshakeKey usage usually has same AAD requirements?
			meta := make([]byte, 3)
			binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
			meta[2] = pkt.Header.Type // Type includes Flags

			data, err = protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
			if err != nil {
				log.Printf("All decryption attempts failed for PID=%d: %v", pkt.Header.PacketID, err)
				return fmt.Errorf("decryption failed: %v", err)
			}
		}
	}
	// On first encrypted command set Connected = true (Fallback if ACK missed)
	if !c.Connected && pkt.Header.PacketID > 2 {
		c.Connected = true
	}

	// Fragment reassembly logic:
	// - First fragment: Fragmented=true, optionally Compressed=true -> start buffer
	// - Middle fragments: Fragmented=false, Compressed=false -> append to buffer
	// - Last fragment: Fragmented=true -> append and process
	isFragmented := pkt.Header.FlagFragmented()

	if isFragmented && !c.Fragmenting {
		// First fragment - start collecting
		c.Fragmenting = true
		c.FragmentBuffer = make([]byte, 0, 4096)
		c.FragmentBuffer = append(c.FragmentBuffer, data...)
		c.FragmentStartPktID = pkt.Header.PacketID
		c.FragmentCompressed = pkt.Header.FlagCompressed()
		log.Printf("Fragment start (PID=%d, Compressed=%v, Len=%d)", pkt.Header.PacketID, c.FragmentCompressed, len(data))
		return nil // Wait for more fragments
	} else if c.Fragmenting && !isFragmented {
		// Middle fragment - append
		c.FragmentBuffer = append(c.FragmentBuffer, data...)
		log.Printf("Fragment continue (PID=%d, TotalLen=%d)", pkt.Header.PacketID, len(c.FragmentBuffer))
		return nil // Wait for more fragments
	} else if c.Fragmenting && isFragmented {
		// Last fragment - complete reassembly
		c.FragmentBuffer = append(c.FragmentBuffer, data...)
		log.Printf("Fragment end (PID=%d, TotalLen=%d)", pkt.Header.PacketID, len(c.FragmentBuffer))
		data = c.FragmentBuffer

		// Decompress if first fragment was compressed
		if c.FragmentCompressed {
			decompressed, err := quicklz.Decompress(data)
			if err != nil {
				log.Printf("QuickLZ decompression of fragmented data failed: %v", err)
				// Fallback to raw data
			} else {
				log.Printf("Decompressed fragmented: %d -> %d bytes", len(data), len(decompressed))
				data = decompressed
			}
		}

		// Reset fragment state
		c.Fragmenting = false
		c.FragmentBuffer = nil
	} else {
		// Non-fragmented packet - decompress if needed
		if pkt.Header.FlagCompressed() {
			decompressed, err := quicklz.Decompress(data)
			if err != nil {
				log.Printf("QuickLZ decompression failed: %v (falling back to raw)", err)
				// Fallback to raw data - might not be compressed despite flag
			} else {
				log.Printf("Decompressed: %d -> %d bytes", len(data), len(decompressed))
				data = decompressed
			}
		}
	}

	cmdStr := string(data)

	// Debug: Log packet flags and raw command preview
	log.Printf("Debug Packet: Compressed=%v, Fragmented=%v, RawLen=%d, Preview=%q",
		pkt.Header.FlagCompressed(), pkt.Header.FlagFragmented(), len(data),
		func() string {
			if len(cmdStr) > 100 {
				return cmdStr[:100]
			}
			return cmdStr
		}())

	// Fix Garbage Headers (TS3 often sends binary garbage before command)
	// Scan for first valid lower case [a-z] char (Most commands are lowercase)
	validStart := strings.IndexFunc(cmdStr, func(r rune) bool {
		return (r >= 'a' && r <= 'z')
	})

	if validStart > 0 && validStart < 50 {
		cmdStr = cmdStr[validStart:]
	}

	log.Printf("Command: %s", cmdStr)

	// Parse Command
	cmd, args := protocol.ParseCommand([]byte(cmdStr))

	switch cmd {
	case "initivexpand2":
		err := c.Handshake.ProcessInitivexpand2(args)
		if err != nil {
			log.Printf("Error processing initivexpand2: %v", err)
		}
	case "initserver":
		// Server sends this after clientinit - contains our clientID
		if cid, ok := args["aclid"]; ok {
			var id uint64
			fmt.Sscanf(cid, "%d", &id)
			c.ClientID = uint16(id)
			log.Printf("Assigned ClientID: %d", c.ClientID)
		}
		if name, ok := args["virtualserver_name"]; ok {
			log.Printf("Server Name: %s", protocol.Unescape(name))
		}
	case "channellist":
		// Parse channel info
		ch := &Channel{}
		if cid, ok := args["cid"]; ok {
			fmt.Sscanf(cid, "%d", &ch.ID)
		}
		if pid, ok := args["cpid"]; ok {
			fmt.Sscanf(pid, "%d", &ch.ParentID)
		}
		if name, ok := args["channel_name"]; ok {
			ch.Name = protocol.Unescape(name)
		}
		if order, ok := args["channel_order"]; ok {
			fmt.Sscanf(order, "%d", &ch.Order)
		}
		c.Channels[ch.ID] = ch
		log.Printf("Channel: [%d] NameRaw=%q Order=%d Args=%v", ch.ID, ch.Name, ch.Order, args)
	case "channellistfinished":
		log.Printf("=== Channel List Complete (%d channels) ===", len(c.Channels))
		var targetChan *Channel
		for _, ch := range c.Channels {
			log.Printf("  - [%d] %s (parent=%d)", ch.ID, ch.Name, ch.ParentID)
			if ch.Name == "Test" {
				targetChan = ch
			}
		}

		if targetChan == nil {
			if ch, ok := c.Channels[2]; ok {
				log.Printf("Name parsing failed. Defaulting to Channel 2 as 'Test'.")
				targetChan = ch
			}
		}

		if targetChan != nil {
			log.Printf("Found target channel 'Test' (ID=%d). Joining...", targetChan.ID)

			if c.ClientID == 0 {
				log.Println("ERROR: ClientID is 0. Cannot join channel. 'initserver' missing?")
				return nil
			}

			moveCmd := protocol.NewCommand("clientmove")
			moveCmd.AddParam("clid", fmt.Sprintf("%d", c.ClientID))
			moveCmd.AddParam("cid", fmt.Sprintf("%d", targetChan.ID))
			moveCmd.AddParam("cpw", "")

			return c.SendCommand(moveCmd)
		}
	case "notifycliententerview":
		// A client entered the server
		nick := ""
		if n, ok := args["client_nickname"]; ok {
			nick = protocol.Unescape(n)
			log.Printf("Client entered: %s", nick)
		}
	case "notifytextmessage":
		// targetmode: 1=Private, 2=Channel, 3=Server
		msg := ""
		invoker := "Unknown"
		if m, ok := args["msg"]; ok {
			msg = protocol.Unescape(m)
		}
		if name, ok := args["invokername"]; ok {
			invoker = protocol.Unescape(name)
		}

		targetMode := "Unknown"
		if tm, ok := args["targetmode"]; ok {
			switch tm {
			case "1":
				targetMode = "Private"
			case "2":
				targetMode = "Channel"
			case "3":
				targetMode = "Server"
			}
		}

		log.Printf("[Chat][%s] %s: %s", targetMode, invoker, msg)

	case "notifyclientchatcomposing":
		// Someone is typing
		// We only get clid, need to map to name if possible, or just log clid
		clid := "Unknown"
		if id, ok := args["clid"]; ok {
			clid = id
		}
		log.Printf("Client %s is typing...", clid)

	case "notifyclientmoved":
		// Client moved to another channel
		clid := args["clid"]
		ctid := args["ctid"]
		// reasonid: 0=switched, 1=moved, 2=timeout, 3=kick, 4=unknown
		log.Printf("Client %s moved to Channel %s", clid, ctid)

	case "notifyclientchannelgroupchanged":
		// Client channel group changed
		// invokerid=0 invokername=Server cgid=8 cid=1 clid=3 cgi=1
		invoker := "Unknown"
		if name, ok := args["invokername"]; ok {
			invoker = protocol.Unescape(name)
		}
		log.Printf("Client %s channel group changed to %s in Channel %s by %s",
			args["clid"], args["cgid"], args["cid"], invoker)

	case "notifyconnectioninforequest":
		// Server asking for connection info. We MUST reply to update Ping in UI and avoid timeout.
		log.Println("Server requested connection info. sending 'setconnectioninfo'...")

		cmd := protocol.NewCommand("setconnectioninfo")
		cmd.AddParam("connection_ping", "50")
		cmd.AddParam("connection_ping_deviation", "5")

		// Detailed stats for each kind as seen in ts3j (KEEPALIVE, SPEECH, CONTROL)
		kinds := []string{"keepalive", "speech", "control"}
		for _, k := range kinds {
			cmd.AddParam("connection_packets_sent_"+k, "500")
			cmd.AddParam("connection_packets_received_"+k, "500")
			cmd.AddParam("connection_bytes_sent_"+k, "25000")
			cmd.AddParam("connection_bytes_received_"+k, "25000")
			cmd.AddParam("connection_bandwidth_sent_last_second_"+k, "200")
			cmd.AddParam("connection_bandwidth_received_last_second_"+k, "200")
			cmd.AddParam("connection_bandwidth_sent_last_minute_"+k, "200")
			cmd.AddParam("connection_bandwidth_received_last_minute_"+k, "200")
			cmd.AddParam("connection_server2client_packetloss_"+k, "0")
		}
		cmd.AddParam("connection_server2client_packetloss_total", "0")

		return c.SendCommand(cmd)

	case "notifyclientupdated":
		// Client updated (e.g. muted/unmuted)
		clid := args["clid"]
		log.Printf("Client %s updated: %v", clid, args)

	case "error":
		// Server reported an error
		id := args["id"]
		msg := protocol.Unescape(args["msg"])
		log.Printf("SERVER ERROR: ID=%s MSG=%s", id, msg)

	case "notifyservergrouplist", "notifychannelgrouplist", "notifyclientneededpermissions":
		// Ignore verbose noisy setup commands
	default:
		log.Printf("Unhandled command: %s Args: %v", cmd, args)
	}

	return nil
}
