package l4

import (
	"bufio"
	"errors"
	"fmt"
	"net"
	"strconv"
	"strings"
)

// ClientInfo is the real client address recovered from a PROXY-protocol
// v1 header.
type ClientInfo struct {
	SrcIP   net.IP
	SrcPort int
	DstIP   net.IP
	DstPort int
}

// WriteProxyV1 emits a HAProxy PROXY-protocol v1 header on dst describing
// the real client (RemoteAddr of `client`) and the local address the
// client connected to. Example:
//
//	PROXY TCP4 203.0.113.7 10.0.0.5 56324 8080\r\n
//
// The header is plain text and human-readable in tcpdump; v2 (binary)
// is what you'd use in production for efficiency and TLV extensions.
func WriteProxyV1(dst net.Conn, client net.Conn) error {
	src, ok1 := client.RemoteAddr().(*net.TCPAddr)
	dstA, ok2 := client.LocalAddr().(*net.TCPAddr)
	if !ok1 || !ok2 {
		return errors.New("l4: PROXY v1 requires TCP addresses")
	}
	fam := "TCP4"
	srcIP, dstIP := src.IP, dstA.IP
	if src.IP.To4() == nil || dstA.IP.To4() == nil {
		fam = "TCP6"
	} else {
		srcIP, dstIP = src.IP.To4(), dstA.IP.To4()
	}
	hdr := fmt.Sprintf("PROXY %s %s %s %d %d\r\n",
		fam, srcIP.String(), dstIP.String(), src.Port, dstA.Port)
	_, err := dst.Write([]byte(hdr))
	return err
}

// ParseProxyV1 consumes a single PROXY-protocol v1 header line from br.
// The same br MUST be used for subsequent reads (it may hold buffered
// payload bytes after the header).
//
// Security: only call this on connections from peers you trust. If an
// untrusted client can send a PROXY header, they can spoof their source
// IP. In production you allowlist the proxy's addresses as trusted
// senders.
func ParseProxyV1(br *bufio.Reader) (ClientInfo, error) {
	line, err := br.ReadString('\n')
	if err != nil {
		return ClientInfo{}, err
	}
	line = strings.TrimRight(line, "\r\n")
	f := strings.Split(line, " ")
	// f = ["PROXY","TCP4","203.0.113.7","10.0.0.5","56324","8080"]
	if len(f) != 6 || f[0] != "PROXY" {
		return ClientInfo{}, fmt.Errorf("l4: bad PROXY v1 header %q", line)
	}
	if f[1] != "TCP4" && f[1] != "TCP6" {
		return ClientInfo{}, fmt.Errorf("l4: unsupported family %q", f[1])
	}
	srcIP := net.ParseIP(f[2])
	dstIP := net.ParseIP(f[3])
	if srcIP == nil || dstIP == nil {
		return ClientInfo{}, errors.New("l4: bad IP in PROXY v1 header")
	}
	srcPort, err1 := strconv.Atoi(f[4])
	dstPort, err2 := strconv.Atoi(f[5])
	if err1 != nil || err2 != nil || srcPort < 0 || srcPort > 65535 || dstPort < 0 || dstPort > 65535 {
		return ClientInfo{}, errors.New("l4: bad port in PROXY v1 header")
	}
	return ClientInfo{SrcIP: srcIP, SrcPort: srcPort, DstIP: dstIP, DstPort: dstPort}, nil
}
