package mc

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"strings"
)

type Handshake struct {
	ProtocolVersion int32
	ServerAddress   string
	ServerPort      uint16
	NextState       int32
	Raw             []byte
}

func ReadHandshake(r io.Reader) (*Handshake, error) {
	raw, err := readPacket(r)
	if err != nil {
		return nil, err
	}

	reader := newReader(raw)
	packetID, err := reader.readVarInt()
	if err != nil {
		return nil, err
	}
	if packetID != 0 {
		return nil, fmt.Errorf("unexpected packet id %d", packetID)
	}

	protocolVersion, err := reader.readVarInt()
	if err != nil {
		return nil, err
	}

	serverAddress, err := reader.readString()
	if err != nil {
		return nil, err
	}

	portBytes, err := reader.readBytes(2)
	if err != nil {
		return nil, err
	}
	serverPort := binary.BigEndian.Uint16(portBytes)

	nextState, err := reader.readVarInt()
	if err != nil {
		return nil, err
	}

	return &Handshake{
		ProtocolVersion: protocolVersion,
		ServerAddress:   serverAddress,
		ServerPort:      serverPort,
		NextState:       nextState,
		Raw:             raw,
	}, nil
}

func ExtractSlug(hostname, playDomain string) string {
	host := strings.ToLower(strings.Split(hostname, "\x00")[0])
	domain := strings.ToLower(playDomain)
	suffix := "." + domain
	if !strings.HasSuffix(host, suffix) {
		return ""
	}
	slug := strings.TrimSuffix(host, suffix)
	if slug == "" || strings.Contains(slug, ".") {
		return ""
	}
	return slug
}

func readPacket(r io.Reader) ([]byte, error) {
	length, err := readVarIntFrom(r)
	if err != nil {
		return nil, err
	}
	if length <= 0 || length > 1<<20 {
		return nil, fmt.Errorf("invalid packet length %d", length)
	}

	payload := make([]byte, length)
	if _, err := io.ReadFull(r, payload); err != nil {
		return nil, err
	}
	return payload, nil
}

type packetReader struct {
	buf *bytes.Reader
}

func newReader(data []byte) *packetReader {
	return &packetReader{buf: bytes.NewReader(data)}
}

func (p *packetReader) readVarInt() (int32, error) {
	return readVarIntFrom(p.buf)
}

func (p *packetReader) readString() (string, error) {
	length, err := p.readVarInt()
	if err != nil {
		return "", err
	}
	if length < 0 || length > 255 {
		return "", fmt.Errorf("invalid string length %d", length)
	}
	data, err := p.readBytes(int(length))
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func (p *packetReader) readBytes(n int) ([]byte, error) {
	out := make([]byte, n)
	if _, err := io.ReadFull(p.buf, out); err != nil {
		return nil, err
	}
	return out, nil
}

func readVarIntFrom(r io.Reader) (int32, error) {
	var numRead int
	var result int32

	for {
		var value [1]byte
		if _, err := io.ReadFull(r, value[:]); err != nil {
			return 0, err
		}
		result |= int32(value[0]&0x7F) << (7 * numRead)
		numRead++
		if numRead > 5 {
			return 0, fmt.Errorf("varint too big")
		}
		if value[0]&0x80 == 0 {
			break
		}
	}

	return result, nil
}

func WritePacket(w io.Writer, payload []byte) error {
	if err := writeVarIntTo(w, int32(len(payload))); err != nil {
		return err
	}
	_, err := w.Write(payload)
	return err
}

func writeVarIntTo(w io.Writer, value int32) error {
	for {
		temp := byte(value & 0x7F)
		value >>= 7
		if value != 0 {
			temp |= 0x80
		}
		if _, err := w.Write([]byte{temp}); err != nil {
			return err
		}
		if value == 0 {
			break
		}
	}
	return nil
}
