package engine

import "net"

type L3Info struct {
	Version  uint8
	Protocol uint8
	IHL      uint8
	SrcIP    [4]byte
	DstIP    [4]byte
	SrcIPv6  [16]byte
	DstIPv6  [16]byte
	Length   uint16
}

func (i L3Info) SrcIPAddr() net.IP {
	if i.Version == 6 {
		return net.IP(i.SrcIPv6[:])
	}
	return net.IP(i.SrcIP[:])
}

func (i L3Info) DstIPAddr() net.IP {
	if i.Version == 6 {
		return net.IP(i.DstIPv6[:])
	}
	return net.IP(i.DstIP[:])
}

type TCPInfo struct {
	SrcPort uint16
	DstPort uint16
	Seq     uint32
	Ack     uint32
	HdrLen  uint8
	SYN     bool
	FIN     bool
	RST     bool
	ACK     bool
}

type UDPInfo struct {
	SrcPort uint16
	DstPort uint16
}

func ParseL3(data []byte) (l3 L3Info, transport []byte, ok bool) {
	if len(data) < 1 {
		return
	}
	version := data[0] >> 4
	switch version {
	case 4:
		if len(data) < 20 {
			return
		}
		ihl := data[0] & 0x0F
		if ihl < 5 || len(data) < int(ihl)*4 {
			return
		}
		totalLen := int(uint16(data[2])<<8 | uint16(data[3]))
		if totalLen < int(ihl)*4 || totalLen > len(data) {
			totalLen = len(data)
		}
		return L3Info{
			Version:  4,
			Protocol: data[9],
			IHL:      ihl,
			Length:   uint16(totalLen),
			SrcIP:    [4]byte{data[12], data[13], data[14], data[15]},
			DstIP:    [4]byte{data[16], data[17], data[18], data[19]},
		}, data[ihl*4 : totalLen], true
	case 6:
		if len(data) < 40 {
			return
		}
		payloadLen := int(uint16(data[4])<<8 | uint16(data[5]))
		totalLen := 40 + payloadLen
		if payloadLen == 0 || totalLen > len(data) {
			totalLen = len(data)
		}
		protocol, tr, ipv6OK := parseIPv6Transport(data, data[6], totalLen)
		if !ipv6OK {
			return
		}
		var srcIP, dstIP [16]byte
		copy(srcIP[:], data[8:24])
		copy(dstIP[:], data[24:40])
		return L3Info{
			Version:  6,
			Protocol: protocol,
			SrcIPv6:  srcIP,
			DstIPv6:  dstIP,
			Length:   uint16(totalLen),
		}, tr, true
	default:
		return
	}
}

func parseIPv6Transport(data []byte, nextHeader uint8, totalLen int) (protocol uint8, transport []byte, ok bool) {
	offset := 40
	proto := nextHeader
	for {
		if offset > totalLen {
			return 0, nil, false
		}
		switch proto {
		case 0, 43, 60: // Hop-by-hop options, Routing, Destination options
			if offset+2 > totalLen {
				return 0, nil, false
			}
			hdrLen := (int(data[offset+1]) + 1) * 8
			if hdrLen < 8 || offset+hdrLen > totalLen {
				return 0, nil, false
			}
			proto = data[offset]
			offset += hdrLen
		case 44: // Fragment
			if offset+8 > totalLen {
				return 0, nil, false
			}
			// Only first fragment carries L4 headers.
			fragOffset := (uint16(data[offset+2])<<8 | uint16(data[offset+3])) >> 3
			if fragOffset != 0 {
				return 0, nil, false
			}
			proto = data[offset]
			offset += 8
		case 51: // Authentication Header
			if offset+2 > totalLen {
				return 0, nil, false
			}
			hdrLen := (int(data[offset+1]) + 2) * 4
			if hdrLen < 8 || offset+hdrLen > totalLen {
				return 0, nil, false
			}
			proto = data[offset]
			offset += hdrLen
		default:
			if offset > totalLen {
				return 0, nil, false
			}
			return proto, data[offset:totalLen], true
		}
	}
}

func ParseTCP(transport []byte) (TCPInfo, []byte, bool) {
	if len(transport) < 20 {
		return TCPInfo{}, nil, false
	}
	dataOff := uint8(transport[12]>>4) * 4
	if dataOff < 20 || len(transport) < int(dataOff) {
		return TCPInfo{}, nil, false
	}
	flags := transport[13]
	payloadLen := len(transport) - int(dataOff)
	return TCPInfo{
		SrcPort: uint16(transport[0])<<8 | uint16(transport[1]),
		DstPort: uint16(transport[2])<<8 | uint16(transport[3]),
		Seq:     uint32(transport[4])<<24 | uint32(transport[5])<<16 | uint32(transport[6])<<8 | uint32(transport[7]),
		Ack:     uint32(transport[8])<<24 | uint32(transport[9])<<16 | uint32(transport[10])<<8 | uint32(transport[11]),
		HdrLen:  dataOff,
		SYN:     flags&0x02 != 0,
		FIN:     flags&0x01 != 0,
		RST:     flags&0x04 != 0,
		ACK:     flags&0x10 != 0,
	}, transport[dataOff : int(dataOff)+payloadLen], true
}

func ParseUDP(transport []byte) (UDPInfo, []byte, bool) {
	if len(transport) < 8 {
		return UDPInfo{}, nil, false
	}
	return UDPInfo{
		SrcPort: uint16(transport[0])<<8 | uint16(transport[1]),
		DstPort: uint16(transport[2])<<8 | uint16(transport[3]),
	}, transport[8:], true
}
