package network

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

	"gitea.boner.be/bdnugget/goonscape/types"
	pb "gitea.boner.be/bdnugget/goonserver/actions"
	rl "github.com/gen2brain/raylib-go/raylib"
	"google.golang.org/protobuf/proto"
)

const protoVersion = 1

var serverAddr = "boner.be:6969"

func SetServerAddr(addr string) {
	serverAddr = addr
}

func ConnectToServer(username, password string, isRegistering bool) (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. Authenticating...")

	// Send auth message
	authAction := &pb.Action{
		Type:     pb.Action_LOGIN,
		Username: username,
		Password: password,
	}
	if isRegistering {
		authAction.Type = pb.Action_REGISTER
	}

	authBatch := &pb.ActionBatch{
		Actions:         []*pb.Action{authAction},
		ProtocolVersion: protoVersion,
	}

	if err := writeMessage(conn, authBatch); err != nil {
		conn.Close()
		return nil, 0, fmt.Errorf("failed to send auth: %v", err)
	}

	// Read server response
	reader := bufio.NewReader(conn)
	lengthBuf := make([]byte, 4)
	if _, err := io.ReadFull(reader, lengthBuf); err != nil {
		conn.Close()
		return nil, 0, fmt.Errorf("failed to read auth response: %v", err)
	}
	messageLength := binary.BigEndian.Uint32(lengthBuf)

	messageBuf := make([]byte, messageLength)
	if _, err := io.ReadFull(reader, messageBuf); err != nil {
		conn.Close()
		return nil, 0, fmt.Errorf("failed to read auth response body: %v", err)
	}

	var response pb.ServerMessage
	if err := proto.Unmarshal(messageBuf, &response); err != nil {
		conn.Close()
		return nil, 0, fmt.Errorf("failed to unmarshal auth response: %v", err)
	}

	if response.ProtocolVersion > protoVersion {
		conn.Close()
		return nil, 0, fmt.Errorf("server requires newer protocol version (server: %d, client: %d)",
			response.ProtocolVersion, protoVersion)
	}

	if !response.AuthSuccess {
		conn.Close()
		return nil, 0, fmt.Errorf(response.ErrorMessage)
	}

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

func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player, quitChan <-chan struct{}) {
	reader := bufio.NewReader(conn)

	actionTicker := time.NewTicker(types.ClientTickRate)
	defer actionTicker.Stop()
	defer conn.Close()
	defer close(player.QuitDone)

	// Create a channel to signal when goroutines are done
	done := make(chan struct{})

	// Create a set of current players to track disconnects
	currentPlayers := make(map[int32]bool)

	go func() {
		for {
			select {
			case <-quitChan:
				// Send disconnect message to server
				disconnectMsg := &pb.ActionBatch{
					PlayerId: playerID,
					Actions: []*pb.Action{{
						Type:     pb.Action_DISCONNECT,
						PlayerId: playerID,
					}},
				}
				writeMessage(conn, disconnectMsg)
				done <- struct{}{}
				return
			case <-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 {
		select {
		case <-quitChan:
			done := make(chan struct{})
			go func() {
				<-done
				close(player.QuitDone)
			}()

			select {
			case <-done:
				time.Sleep(100 * time.Millisecond)
			case <-time.After(1 * time.Second):
				log.Println("Shutdown timed out")
			}
			return
		default:
			// 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 {
				currentPlayers[state.PlayerId] = true
				if state.PlayerId == playerID {
					player.Lock()
					// Update initial position if not set
					if player.PosActual.X == 0 && player.PosActual.Z == 0 {
						player.PosActual = rl.Vector3{
							X: float32(state.X * types.TileSize),
							Y: 0,
							Z: float32(state.Y * types.TileSize),
						}
						player.PosTile = types.Tile{X: int(state.X), Y: int(state.Y)}
					}
					player.Unlock()
					continue
				}

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

			// Remove players that are no longer in the server state
			for id := range otherPlayers {
				if !currentPlayers[id] {
					delete(otherPlayers, id)
				}
			}

			if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 {
				handler.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
}
