package network

import (
	"bufio"
	"encoding/binary"
	"io"
	"log"
	"net"
	"time"

	"gitea.boner.be/bdnugget/goonscape/game"
	"gitea.boner.be/bdnugget/goonscape/types"
	pb "gitea.boner.be/bdnugget/goonserver/actions"
	"google.golang.org/protobuf/proto"
)

var serverAddr = "boner.be:6969"

func SetServerAddr(addr string) {
	serverAddr = addr
}

func ConnectToServer() (net.Conn, int32, error) {
	conn, err := net.Dial("tcp", serverAddr)
	if err != nil {
		log.Printf("Failed to dial server: %v", err)
		return nil, 0, err
	}

	log.Println("Connected to server. Waiting for player ID...")
	reader := bufio.NewReader(conn)

	// Read message length (4 bytes)
	lengthBuf := make([]byte, 4)
	if _, err := io.ReadFull(reader, lengthBuf); err != nil {
		log.Printf("Failed to read message length: %v", err)
		return nil, 0, err
	}
	messageLength := binary.BigEndian.Uint32(lengthBuf)

	// Read the full message
	messageBuf := make([]byte, messageLength)
	if _, err := io.ReadFull(reader, messageBuf); err != nil {
		log.Printf("Failed to read message body: %v", err)
		return nil, 0, err
	}

	var response pb.ServerMessage
	if err := proto.Unmarshal(messageBuf, &response); err != nil {
		log.Printf("Failed to unmarshal server response: %v", err)
		return nil, 0, err
	}

	playerID := response.GetPlayerId()
	log.Printf("Successfully connected with player ID: %d", playerID)
	return conn, playerID, nil
}

func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player) {
	// Create a buffered reader for the connection
	reader := bufio.NewReader(conn)

	actionTicker := time.NewTicker(types.ClientTickRate)
	defer actionTicker.Stop()

	go func() {
		for range actionTicker.C {
			player.Lock()
			if len(player.ActionQueue) > 0 {
				actions := make([]*pb.Action, len(player.ActionQueue))
				copy(actions, player.ActionQueue)

				batch := &pb.ActionBatch{
					PlayerId: playerID,
					Actions:  actions,
					Tick:     player.CurrentTick,
				}

				player.ActionQueue = player.ActionQueue[:0]
				player.Unlock()

				if err := writeMessage(conn, batch); err != nil {
					log.Printf("Failed to send actions to server: %v", err)
					return
				}
			} else {
				player.Unlock()
			}
		}
	}()

	for {
		// Read message length (4 bytes)
		lengthBuf := make([]byte, 4)
		if _, err := io.ReadFull(reader, lengthBuf); err != nil {
			log.Printf("Failed to read message length: %v", err)
			return
		}
		messageLength := binary.BigEndian.Uint32(lengthBuf)

		// Read the full message
		messageBuf := make([]byte, messageLength)
		if _, err := io.ReadFull(reader, messageBuf); err != nil {
			log.Printf("Failed to read message body: %v", err)
			return
		}

		var serverMessage pb.ServerMessage
		if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil {
			log.Printf("Failed to unmarshal server message: %v", err)
			continue
		}

		player.Lock()
		player.CurrentTick = serverMessage.CurrentTick

		tickDiff := serverMessage.CurrentTick - player.CurrentTick
		if tickDiff > types.MaxTickDesync {
			for _, state := range serverMessage.Players {
				if state.PlayerId == playerID {
					player.ForceResync(state)
					break
				}
			}
		}
		player.Unlock()

		for _, state := range serverMessage.Players {
			if state.PlayerId == playerID {
				continue
			}

			if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
				otherPlayer.UpdatePosition(state, types.ServerTickRate)
			} else {
				otherPlayers[state.PlayerId] = types.NewPlayer(state)
			}
		}

		if g, ok := player.UserData.(*game.Game); ok && len(serverMessage.ChatMessages) > 0 {
			g.Chat.HandleServerMessages(serverMessage.ChatMessages)
		}
	}
}

// Helper function to write length-prefixed messages
func writeMessage(conn net.Conn, msg proto.Message) error {
	data, err := proto.Marshal(msg)
	if err != nil {
		return err
	}

	// Write length prefix
	lengthBuf := make([]byte, 4)
	binary.BigEndian.PutUint32(lengthBuf, uint32(len(data)))
	if _, err := conn.Write(lengthBuf); err != nil {
		return err
	}

	// Write message body
	_, err = conn.Write(data)
	return err
}
