package network

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

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

const protoVersion = 1

var serverAddr = "boner.be:6969"

type NetworkManager struct {
	ctx    context.Context
	conn   net.Conn
	reader *bufio.Reader
	writer *bufio.Writer
	mu     sync.Mutex
}

func NewNetworkManager(ctx context.Context) *NetworkManager {
	return &NetworkManager{
		ctx: ctx,
	}
}

func SetServerAddr(addr string) {
	serverAddr = addr
}

func (nm *NetworkManager) Connect(addr string) error {
	nm.mu.Lock()
	defer nm.mu.Unlock()

	var err error
	nm.conn, err = net.Dial("tcp", addr)
	if err != nil {
		return err
	}

	nm.reader = bufio.NewReader(nm.conn)
	nm.writer = bufio.NewWriter(nm.conn)

	go nm.readLoop()
	return nil
}

func (nm *NetworkManager) readLoop() {
	for {
		select {
		case <-nm.ctx.Done():
			return
		default:
			// Read and process messages
		}
	}
}

func (nm *NetworkManager) Send(message proto.Message) error {
	nm.mu.Lock()
	defer nm.mu.Unlock()

	data, err := proto.Marshal(message)
	if err != nil {
		return err
	}

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

	// Write message body
	if _, err := nm.writer.Write(data); err != nil {
		return err
	}

	return nm.writer.Flush()
}

func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) {
	logging.Info.Println("Attempting to connect to server at", serverAddr)
	conn, err := net.Dial("tcp", serverAddr)
	if err != nil {
		logging.Error.Printf("Failed to dial server: %v", err)
		return nil, 0, err
	}
	logging.Info.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("authentication failed: %s", 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 *sync.Map, quitChan <-chan struct{}) {
	defer func() {
		logging.Info.Println("Closing connection and cleaning up for player", playerID)
		conn.Close()
		close(player.QuitDone)
	}()

	reader := bufio.NewReader(conn)
	ticker := time.NewTicker(100 * time.Millisecond)
	defer ticker.Stop()

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

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

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

			// Update player states
			for _, state := range serverMessage.Players {
				if state == nil {
					logging.Error.Println("Received nil player state")
					continue
				}

				if state.PlayerId == playerID {
					player.UpdatePosition(state, types.ServerTickRate)
				} else if existing, ok := otherPlayers.Load(state.PlayerId); ok {
					existing.(*types.Player).UpdatePosition(state, types.ServerTickRate)
				} else {
					newPlayer := types.NewPlayer(state)
					otherPlayers.Store(state.PlayerId, newPlayer)
				}
			}

			// Handle chat messages
			if handler, ok := player.UserData.(types.ChatMessageHandler); ok {
				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
}
