package dns

import (
	"bytes"
	"encoding/binary"
	"math"
	"slices"
	"strconv"
	"strings"
)

// Global parameters.
const (
	// SizeHeader is the length (in bytes) of a DNS header.
	// A header is comprised of 6 uint16s and no padding.
	SizeHeader = 6 * 2
	// The Internet supports name server access using TCP [RFC-9293] on server
	// port 53 (decimal) as well as datagram access using UDP [RFC-768] on UDP port 53 (decimal).
	ServerPort = 53
	ClientPort = 53
	// Messages carried by UDP are restricted to 512 bytes (not counting the IP
	// or UDP headers).  Longer messages are truncated and the TC bit is set in the header.
	MaxSizeUDP = 512
)

type Message struct {
	Questions   []Question
	Answers     []Resource
	Authorities []Resource
	Additionals []Resource
}

type Question struct {
	Name  Name
	Type  Type
	Class Class
}

type Resource struct {
	Header ResourceHeader
	data   []byte
}

// A ResourceHeader is the header of a DNS resource record. There are
// many types of DNS resource records, but they all share the same header.
type ResourceHeader struct {
	Name   Name
	Type   Type
	Class  Class
	TTL    uint32
	Length uint16
}

type Name struct {
	data []byte
}

// Decode decodes the DNS message in b into m. It returns the number of bytes
// consumed from b (0 if no bytes were consumed) and any error encountered.
// If the message was not completely parsed due to LimitResourceDecoding,
// incompleteButOK is true and an error is returned, though the message is still usable.
func (m *Message) Decode(msg []byte) (_ uint16, incompleteButOK bool, err error) {
	if len(msg) < SizeHeader {
		return 0, false, errBaseLen
	} else if len(msg) > math.MaxUint16 {
		return 0, false, errResTooLong
	}
	m.Reset()
	hdr := NewFrame(msg)
	nq := int(hdr.QDCount())
	off := uint16(SizeHeader)
	// Return tooManyErr if found to flag to the caller that the message was
	// decoded but contained too many resources to decode completely.

	var tooManyErr error
	switch {
	case nq > cap(m.Questions):
		tooManyErr = errTooManyQuestions
	case hdr.ANCount() > uint16(cap(m.Answers)):
		tooManyErr = errTooManyAnswers
	case hdr.NSCount() > uint16(cap(m.Authorities)):
		tooManyErr = errTooManyAuthorities
	case hdr.ARCount() > uint16(cap(m.Additionals)):
		tooManyErr = errTooManyAdditionals
	}
	if nq > cap(m.Questions) {
		nq = cap(m.Questions)
	}
	m.Questions = m.Questions[:nq]
	for i := 0; i < nq; i++ {
		off, err = m.Questions[i].Decode(msg, off)
		if err != nil {
			m.Questions = m.Questions[:i] // Trim non-decoded/failed questions.
			return off, false, err
		}
	}
	// Skip undecoded questions.
	for i := 0; i < int(hdr.QDCount())-nq; i++ {
		off, err = skipQuestion(msg, off)
		if err != nil {
			return off, false, err
		}
	}

	off, err = decodeToCapResources(&m.Answers, msg, hdr.ANCount(), off)
	if err != nil {
		return off, false, err
	}
	off, err = decodeToCapResources(&m.Authorities, msg, hdr.NSCount(), off)
	if err != nil {
		return off, false, err
	}
	off, err = decodeToCapResources(&m.Additionals, msg, hdr.ARCount(), off)
	if err != nil {
		return off, false, err
	}
	return off, tooManyErr != nil, tooManyErr
}

func decodeToCapResources(dst *[]Resource, msg []byte, nrec, off uint16) (_ uint16, err error) {
	originalRec := nrec
	if nrec > uint16(cap(*dst)) {
		nrec = uint16(cap(*dst)) // Decode up to cap. Caller will return an error flag.
	}
	*dst = (*dst)[:nrec]
	for i := uint16(0); i < nrec; i++ {
		off, err = (*dst)[i].Decode(msg, off)
		if err != nil {
			*dst = (*dst)[:i] // Trim non-decoded/failed resources.
			return off, err
		}
	}
	// Parse undecoded resources, effectively skipping them.
	for i := uint16(0); i < originalRec-nrec; i++ {
		off, err = skipResource(msg, off)
		if err != nil {
			return off, err
		}
	}
	return off, nil
}

func skipQuestion(msg []byte, off uint16) (_ uint16, err error) {
	off, err = skipName(msg, off)
	if err != nil {
		return off, err
	}
	if off+4 > uint16(len(msg)) {
		return off, errBaseLen
	}
	return off + 4, nil
}

func skipResource(msg []byte, off uint16) (_ uint16, err error) {
	off, err = skipName(msg, off)
	if err != nil {
		return off, err
	}
	// | Name... | Type16 | Class16 | TTL32 | Length16 | Data... |
	datalen := binary.BigEndian.Uint16(msg[off+8:])
	off += datalen + 10
	if off > uint16(len(msg)) {
		return off, errBaseLen
	}
	return off, nil
}

func skipName(msg []byte, off uint16) (uint16, error) {
	return visitAllLabels(msg, off, func(b []byte) {}, allowCompression)
}

func (m *Message) AppendTo(buf []byte, txid uint16, flags HeaderFlags) (_ []byte, err error) {
	nq := uint16(len(m.Questions))
	nans := uint16(len(m.Answers))
	nauth := uint16(len(m.Authorities))
	nadd := uint16(len(m.Additionals))
	var hdr [SizeHeader]byte
	f := NewFrame(hdr[:])
	f.SetTxID(txid)
	f.SetFlags(flags)
	f.SetQDCount(nq)
	f.SetANCount(nans)
	f.SetNSCount(nauth)
	f.SetARCount(nadd)

	buf = slices.Grow(buf, int(m.Len()))
	buf = append(buf, hdr[:]...)
	for _, q := range m.Questions {
		buf, err = q.appendTo(buf)
		if err != nil {
			return buf, err
		}
	}
	for _, r := range m.Answers {
		buf, err = r.appendTo(buf)
		if err != nil {
			return buf, err
		}
	}
	for _, r := range m.Authorities {
		buf, err = r.appendTo(buf)
		if err != nil {
			return buf, err
		}
	}
	for _, r := range m.Additionals {
		buf, err = r.appendTo(buf)
		if err != nil {
			return buf, err
		}
	}
	return buf, nil
}

func (m *Message) Len() uint16 {
	return SizeHeader + m.lenResources()
}

func (m *Message) lenResources() (l uint16) {
	for i := range m.Questions {
		l += m.Questions[i].Len()
	}
	for i := range m.Answers {
		l += m.Answers[i].Len()
	}
	for i := range m.Authorities {
		l += m.Authorities[i].Len()
	}
	for i := range m.Additionals {
		l += m.Additionals[i].Len()
	}
	return l
}

func (m *Message) AddQuestions(questions []Question) {
	// This question slice handling here is done in spirit of DNSClient being owner of its own buffer.
	// If this is not done we risk the Questions being edited by user and interfering with the DNS request.
	qoff := len(m.Questions)
	m.Questions = slices.Grow(m.Questions, len(questions))
	m.Questions = m.Questions[:qoff+len(questions)]
	for i := range questions {
		m.Questions[qoff+i].Name.CloneFrom(questions[i].Name)
		m.Questions[qoff+i].Type = questions[i].Type
		m.Questions[qoff+i].Class = questions[i].Class
	}
}

func (m *Message) LimitResourceDecoding(maxQ, maxAns, maxAuth, maxAdd uint16) {
	m.Questions = slices.Grow(m.Questions, int(maxQ))
	m.Answers = slices.Grow(m.Answers, int(maxQ))
	m.Authorities = slices.Grow(m.Authorities, int(maxQ))
	m.Additionals = slices.Grow(m.Additionals, int(maxQ))
}

func (m *Message) Reset() {
	m.Questions = m.Questions[:0]
	m.Answers = m.Answers[:0]
	m.Authorities = m.Authorities[:0]
	m.Additionals = m.Additionals[:0]
}

// String returns a string representation of the header.
func (h *ResourceHeader) String() string {
	return h.Name.String() + " " + h.Type.String() + " " + h.Class.String() +
		" ttl=" + strconv.FormatUint(uint64(h.TTL), 10) + " len=" + strconv.FormatUint(uint64(h.Length), 10)
}

func (r *Resource) Reset() {
	r.Header.Reset()
	r.data = r.data[:0]
}

func (r *Resource) Len() uint16 {
	return r.Header.Name.Len() + 10 + uint16(len(r.data))
}

func (r *Resource) RawData() []byte {
	length := r.Header.Length
	if int(length) > len(r.data) {
		length = uint16(len(r.data))
	}
	return r.data[:length]
}

func (q *Question) Reset() {
	q.Name.Reset()
	*q = Question{Name: q.Name} // Reuse Name's buffer.
}

// Len returns Question's length over-the-wire.
func (q *Question) Len() uint16 { return q.Name.Len() + 4 }

func (r *ResourceHeader) Reset() {
	r.Name.Reset()
	*r = ResourceHeader{Name: r.Name} // Reuse Name's buffer.
}

func (q *Question) Decode(msg []byte, off uint16) (uint16, error) {
	off, err := q.Name.Decode(msg, off)
	if err != nil {
		return off, err
	}
	if off+4 > uint16(len(msg)) {
		return off, errResourceLen
	}
	q.Type = Type(binary.BigEndian.Uint16(msg[off:]))
	q.Class = Class(binary.BigEndian.Uint16(msg[off+2:]))
	return off + 4, nil
}

func (q *Question) appendTo(buf []byte) (_ []byte, err error) {
	buf, err = q.Name.AppendTo(buf)
	if err != nil {
		return buf, err
	}
	buf = append16(buf, uint16(q.Type))
	buf = append16(buf, uint16(q.Class))
	return buf, nil
}

// String returns a string representation of the Question with the Name in dotted format.
func (q *Question) String() string {
	return q.Name.String() + " " + q.Type.String() + " " + q.Class.String()
}

func (r *Resource) Decode(b []byte, off uint16) (uint16, error) {
	off, err := r.Header.Decode(b, off)
	if err != nil {
		return off, err
	}
	if r.Header.Length > uint16(len(b[off:])) {
		return off, errResourceLen
	}
	r.data = append(r.data[:0], b[off:off+r.Header.Length]...)
	return off + r.Header.Length, nil
}

func (r *Resource) appendTo(buf []byte) (_ []byte, err error) {
	r.Header.Length = uint16(len(r.data))
	buf, err = r.Header.appendTo(buf)
	if err != nil {
		return buf, err
	}
	buf = append(buf, r.data...)
	return buf, nil
}

func (rhdr *ResourceHeader) Decode(msg []byte, off uint16) (uint16, error) {
	off, err := rhdr.Name.Decode(msg, off)
	if err != nil {
		return off, err
	}
	if off+10 > uint16(len(msg)) {
		return off, errResourceLen
	}
	rhdr.Type = Type(binary.BigEndian.Uint16(msg[off:]))     // 2
	rhdr.Class = Class(binary.BigEndian.Uint16(msg[off+2:])) // 4
	rhdr.TTL = binary.BigEndian.Uint32(msg[off+4:])          // 8
	rhdr.Length = binary.BigEndian.Uint16(msg[off+8:])       // 10
	return off + 10, nil
}

func (rhdr *ResourceHeader) appendTo(buf []byte) (_ []byte, err error) {
	buf, err = rhdr.Name.AppendTo(buf)
	if err != nil {
		return buf, err
	}
	buf = append16(buf, uint16(rhdr.Type))
	buf = append16(buf, uint16(rhdr.Class))
	buf = append32(buf, rhdr.TTL)
	buf = append16(buf, rhdr.Length)
	return buf, nil
}

func MustNewName(s string) Name {
	name, err := NewName(s)
	if err != nil {
		panic(err)
	}
	return name
}

// NewName parses a domain name and returns a new Name.
func NewName(domain string) (Name, error) {
	if len(domain) == 1 && domain[0] == '.' {
		return Name{data: []byte{0}}, nil
	}
	var name Name
	for len(domain) > 0 {
		idx := strings.IndexByte(domain, '.')
		done := idx < 0 || idx+1 > len(domain)
		if done {
			idx = len(domain)
		}
		if !name.CanAddLabel(domain[:idx]) {
			return Name{}, errCantAddLabel
		}
		name.AddLabel(domain[:idx])
		if done {
			break
		}
		domain = domain[idx+1:]
	}
	return name, nil
}

// Len returns the length over-the-wire of the encoded Name.
func (n *Name) Len() uint16 {
	return uint16(len(n.data))
}

func (n *Name) CloneFrom(ex Name) {
	n.data = append(n.data[:0], ex.data...)
}

// AppendTo appends the Name to b in wire format and returns the resulting slice.
func (n *Name) AppendTo(b []byte) ([]byte, error) {
	if len(n.data) == 0 {
		return b, errInvalidName
	}
	return append(b, n.data...), nil
}

// String returns a string representation of the name in dotted format.
func (n *Name) String() string {
	b := make([]byte, 0, len(n.data)+3)
	return string(n.AppendDottedTo(b))
}

// AppendDottedTo appends the Name to b in dotted format and returns the resulting slice.
func (n *Name) AppendDottedTo(b []byte) []byte {
	n.VisitLabels(func(label []byte) {
		b = append(b, label...)
		b = append(b, '.')
	})
	return b
}

// Decode resets internal Name buffer and reads raw wire data from buffer, returning any error encountered.
func (n *Name) Decode(b []byte, off uint16) (uint16, error) {
	n.Reset()
	off, err := visitAllLabels(b, off, n.vistAddLabel, allowCompression)
	if err != nil {
		n.Reset()
		return off, err
	}
	n.data = append(n.data, 0) // Add terminator, off counts the terminator already in visitAllLabels.
	return off, nil
}

// Reset resets the Name labels to be empty and reuses buffer.
func (n *Name) Reset() { n.data = n.data[:0] }

// CanAddLabel reports whether the label can be added to the name.
func (n *Name) CanAddLabel(label string) bool {
	return len(label) != 0 && len(label) <= 63 && len(label)+len(n.data)+2 <= 255 && // Include len+terminator+label.
		label[len(label)-1] != 0 && // We do not support implicitly zero-terminated labels.
		strings.IndexByte(label, '.') < 0 // See issue golang/go#56246
}

// AddLabel adds a label to the name. If n.CanAddLabel(label) returns false, it panics.
func (n *Name) AddLabel(label string) {
	if !n.CanAddLabel(label) {
		panic(errCantAddLabel.Error())
	}
	if n.isTerminated() {
		n.data = n.data[:len(n.data)-1] // Remove terminator if present to add another label.
	}
	n.data = append(n.data, byte(len(label)))
	n.data = append(n.data, label...)
	n.data = append(n.data, 0)
}

func (n *Name) vistAddLabel(label []byte) {
	n.data = append(n.data, byte(len(label)))
	n.data = append(n.data, label...)
}

func (n *Name) isTerminated() bool {
	return len(n.data) > 0 && n.data[len(n.data)-1] == 0
}

func (n *Name) VisitLabels(fn func(label []byte)) error {
	if len(n.data) > 255 {
		return errNameTooLong
	}
	_, err := visitAllLabels(n.data, 0, fn, allowCompression)
	return err
}

func append16(b []byte, v uint16) []byte {
	binary.BigEndian.PutUint16(b[len(b):len(b)+2], v)
	return b[:len(b)+2]
}

func append32(b []byte, v uint32) []byte {
	binary.BigEndian.PutUint32(b[len(b):len(b)+4], v)
	return b[:len(b)+4]
}

func visitAllLabels(msg []byte, off uint16, fn func(b []byte), allowCompression bool) (uint16, error) {
	// currOff is the current working offset.
	currOff := off
	if len(msg) > math.MaxUint16 {
		return off, errResTooLong
	}
	// ptr is the number of pointers followed.
	var ptr uint8
	// newOff is the offset where the next record will start. Pointers lead
	// to data that belongs to other names and thus doesn't count towards to
	// the usage of this name.
	var newOff = off

LOOP:
	for {
		if currOff >= uint16(len(msg)) {
			return off, errBaseLen
		}
		c := uint16(msg[currOff])
		currOff++
		switch c & 0xc0 {
		case 0x00: // String label (segment).
			if c == 0x00 {
				break LOOP // Nominal end of name, always ends with null terminator.
			}
			endOff := currOff + c
			if endOff > uint16(len(msg)) {
				return off, errCalcLen
			}

			// Reject names containing dots. See issue golang/go#56246
			if bytes.IndexByte(msg[currOff:endOff], '.') >= 0 {
				return off, errInvalidName
			}

			fn(msg[currOff:endOff])
			currOff = endOff

		case 0xc0: // Pointer.
			// https://cs.opensource.google/go/x/net/+/refs/tags/v0.19.0:dns/dnsmessage/message.go;l=2078
			if !allowCompression {
				return off, errCompressedSRV
			}
			if currOff >= uint16(len(msg)) {
				return off, errInvalidPtr
			}
			c1 := msg[currOff]
			currOff++
			if ptr == 0 {
				newOff = currOff
			}
			// Don't follow too many pointers, maybe there's a loop.
			if ptr++; ptr > 10 {
				return off, errTooManyPtr
			}
			currOff = (c^0xC0)<<8 | uint16(c1)
		default:
			// Prefixes 0x80 and 0x40 are reserved.
			return off, errReserved
		}
	}
	if ptr == 0 {
		newOff = currOff
	}
	return newOff, nil
}
