package icmp

import (
	"bytes"
	"errors"
	"fmt"

	"golang.org/x/net/icmp"
)

var (
	ErrNextHopMTUTooLow  = errors.New("ICMP Next Hop MTU is too low")
	ErrNextHopMTUTooHigh = errors.New("ICMP Next Hop MTU is too high")
)

func checkMTU(mtu, minMTU, physicalLinkMTU uint32) (err error) {
	switch {
	case mtu < minMTU:
		return fmt.Errorf("%w: %d", ErrNextHopMTUTooLow, mtu)
	case mtu > physicalLinkMTU:
		return fmt.Errorf("%w: %d is larger than physical link MTU %d",
			ErrNextHopMTUTooHigh, mtu, physicalLinkMTU)
	default:
		return nil
	}
}

func checkInvokingReplyIDMatch(icmpProtocol int, received []byte,
	outboundMessage *icmp.Message,
) (match bool, err error) {
	inboundMessage, err := icmp.ParseMessage(icmpProtocol, received)
	if err != nil {
		return false, fmt.Errorf("parsing invoking packet: %w", err)
	}
	inboundBody, ok := inboundMessage.Body.(*icmp.Echo)
	if !ok {
		return false, fmt.Errorf("%w: %T", ErrBodyUnsupported, inboundMessage.Body)
	}
	outboundBody := outboundMessage.Body.(*icmp.Echo) //nolint:forcetypeassert
	return inboundBody.ID == outboundBody.ID, nil
}

var ErrIDMismatch = errors.New("ICMP id mismatch")

func checkEchoReply(icmpProtocol int, received []byte,
	outboundMessage *icmp.Message, truncatedBody bool,
) (err error) {
	inboundMessage, err := icmp.ParseMessage(icmpProtocol, received)
	if err != nil {
		return fmt.Errorf("parsing invoking packet: %w", err)
	}
	inboundBody, ok := inboundMessage.Body.(*icmp.Echo)
	if !ok {
		return fmt.Errorf("%w: %T", ErrBodyUnsupported, inboundMessage.Body)
	}
	outboundBody := outboundMessage.Body.(*icmp.Echo) //nolint:forcetypeassert
	if inboundBody.ID != outboundBody.ID {
		return fmt.Errorf("%w: sent id %d and received id %d",
			ErrIDMismatch, outboundBody.ID, inboundBody.ID)
	}
	err = checkEchoBodies(outboundBody.Data, inboundBody.Data, truncatedBody)
	if err != nil {
		return fmt.Errorf("checking sent and received bodies: %w", err)
	}
	return nil
}

var ErrEchoDataMismatch = errors.New("ICMP data mismatch")

func checkEchoBodies(sent, received []byte, receivedTruncated bool) (err error) {
	if len(received) > len(sent) {
		return fmt.Errorf("%w: sent %d bytes and received %d bytes",
			ErrEchoDataMismatch, len(sent), len(received))
	}
	if receivedTruncated {
		sent = sent[:len(received)]
	}
	if !bytes.Equal(received, sent) {
		return fmt.Errorf("%w: sent %x and received %x",
			ErrEchoDataMismatch, sent, received)
	}
	return nil
}
