package ip

import (
	"encoding/binary"
	"net/netip"

	"github.com/qdm12/gluetun/internal/pmtud/constants"
)

func HeaderLength(ipv4 bool) uint32 {
	if ipv4 {
		return constants.IPv4HeaderLength
	}
	return constants.IPv6HeaderLength
}

func HeaderV4(srcIP, dstIP netip.Addr, payloadLength uint32) []byte {
	ipHeader := make([]byte, constants.IPv4HeaderLength)
	const version byte = 4
	const headerLength byte = 20 / 4                                          // in 32-bit words
	ipHeader[0] = (version << 4) | headerLength                               //nolint:mnd
	ipHeader[1] = 0                                                           // type of Service
	putUint16(ipHeader[2:], uint16(constants.IPv4HeaderLength+payloadLength)) //nolint:gosec
	ipHeader[4], ipHeader[5] = 0, 0                                           // identification
	const flagsAndOffset uint16 = 0x4000                                      // DF bit set
	putUint16(ipHeader[6:], flagsAndOffset)
	ipHeader[8] = 64 // ttl
	ipHeader[9] = constants.IPPROTO_TCP
	srcIPBytes := srcIP.As4()
	copy(ipHeader[12:16], srcIPBytes[:])
	dstIPBytes := dstIP.As4()
	copy(ipHeader[16:20], dstIPBytes[:])

	checksum := ipChecksum(ipHeader)
	ipHeader[10] = byte(checksum >> 8)   //nolint:mnd
	ipHeader[11] = byte(checksum & 0xff) //nolint:mnd

	return ipHeader
}

// ipChecksum calculates the checksum for the IP header.
//
//nolint:mnd
func ipChecksum(header []byte) uint16 {
	sum := uint32(0)
	for i := 0; i < len(header)-1; i += 2 {
		sum += uint32(header[i])<<8 + uint32(header[i+1])
	}
	if len(header)%2 != 0 {
		sum += uint32(header[len(header)-1]) << 8
	}
	for (sum >> 16) > 0 {
		sum = (sum & 0xFFFF) + (sum >> 16)
	}
	return ^uint16(sum) //nolint:gosec
}

// HeaderV6 makes an IPv6 header.
// payloadLen is the length of the payload following the header.
// nextHeader can be byte([constants.IPPROTO_TCP]) for example.
func HeaderV6(srcIP, dstIP netip.Addr,
	payloadLen uint16, nextHeader byte,
) []byte {
	ipv6Header := make([]byte, constants.IPv6HeaderLength)
	ipv6Header[0] = 0x60 // version (4 bits) | traffic Class (4 bits)
	ipv6Header[1] = 0x00 // traffic Class (4 bits) | flow label (4 bits)

	// Flow Label (remaining 16 bits)
	ipv6Header[2] = 0x00
	ipv6Header[3] = 0x00

	binary.BigEndian.PutUint16(ipv6Header[4:], payloadLen)
	ipv6Header[6] = nextHeader
	const hopLimit = 64
	ipv6Header[7] = hopLimit
	copy(ipv6Header[8:24], srcIP.AsSlice())
	copy(ipv6Header[24:40], dstIP.AsSlice())
	return ipv6Header
}
