package network

import (
	"errors"
	"fmt"

	"regionio/internal/protocol"
	"regionio/internal/server"
)

// handleLogin drives the offline-mode login phase:
//
//	C→S Login Start        → derive offline profile
//	S→C Set Compression    → (optional) enable zlib for later packets
//	S→C Login Success      → confirm the profile
//	C→S Login Acknowledged → switch to the Configuration phase
//
// Encryption (online mode) is intentionally not implemented here.
func (h *handler) handleLogin(pkt protocol.Packet) error {
	switch pkt.ID {
	case protocol.LoginStartID:
		return h.handleLoginStart(pkt)
	case protocol.LoginAcknowledgedID:
		// Client acknowledges Login Success; both sides enter Configuration.
		h.conn.SetState(protocol.StateConfiguration)
		h.log.Info("player logged in",
			"name", h.conn.Profile.Name,
			"uuid", uuidString(h.conn.Profile.UUID))
		return h.beginConfiguration()
	default:
		return errors.New("unexpected packet in login state")
	}
}

func (h *handler) handleLoginStart(pkt protocol.Packet) error {
	if h.protocolVersion != protocol.ProtocolVersion {
		return fmt.Errorf("unsupported protocol %d, want %d", h.protocolVersion, protocol.ProtocolVersion)
	}
	r := pkt.Body()
	name, err := r.String()
	if err != nil {
		return err
	}
	if !validPlayerName(name) {
		return errors.New("invalid login name")
	}
	// The client also sends a UUID, but in offline mode we derive our own so it
	// is stable and independent of what the client claims.
	if _, err := r.UUID(); err != nil {
		return err
	}

	h.conn.Profile = server.Profile{
		UUID: server.OfflineUUID(name),
		Name: name,
	}

	// Negotiate compression before Login Success so that packet (and every
	// later one) is sent in the compressed format.
	if t := h.srv.Config().CompressionThreshold; t >= 0 {
		w := protocol.NewWriter(protocol.VarIntLen(int32(t)))
		w.VarInt(int32(t))
		if err := h.conn.SendWriter(protocol.SetCompressionID, w); err != nil {
			return err
		}
		h.conn.EnableCompression(int32(t))
	}

	return h.sendLoginSuccess()
}

func validPlayerName(name string) bool {
	if len(name) == 0 || len(name) > 16 {
		return false
	}
	for _, r := range name {
		if r != '_' && (r < '0' || r > '9') && (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') {
			return false
		}
	}
	return true
}

// sendLoginSuccess writes the Login Success packet. For protocol 775 the body
// is: UUID, Username, then a VarInt-prefixed array of profile properties (none
// in offline mode).
func (h *handler) sendLoginSuccess() error {
	p := h.conn.Profile
	w := protocol.NewWriter(16 + len(p.Name) + 2)
	w.UUID(p.UUID)
	w.String(p.Name)
	w.VarInt(0) // property count
	return h.conn.SendWriter(protocol.LoginSuccessID, w)
}

// uuidString formats a UUID as the canonical 8-4-4-4-12 hex string.
func uuidString(u [16]byte) string {
	const hexdigits = "0123456789abcdef"
	var b [36]byte
	j := 0
	for i := 0; i < 16; i++ {
		if i == 4 || i == 6 || i == 8 || i == 10 {
			b[j] = '-'
			j++
		}
		b[j] = hexdigits[u[i]>>4]
		b[j+1] = hexdigits[u[i]&0x0f]
		j += 2
	}
	return string(b[:j])
}
