// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
	"fmt"
	"strings"
)

// A SenderReport (SR) packet provides reception quality feedback for an RTP stream.
type SenderReport struct {
	// The synchronization source identifier for the originator of this SR packet.
	SSRC uint32
	// The wallclock time when this report was sent so that it may be used in
	// combination with timestamps returned in reception reports from other
	// receivers to measure round-trip propagation to those receivers.
	NTPTime uint64
	// Corresponds to the same time as the NTP timestamp (above), but in
	// the same units and with the same random offset as the RTP
	// timestamps in data packets. This correspondence may be used for
	// intra- and inter-media synchronization for sources whose NTP
	// timestamps are synchronized, and may be used by media-independent
	// receivers to estimate the nominal RTP clock frequency.
	RTPTime uint32
	// The total number of RTP data packets transmitted by the sender
	// since starting transmission up until the time this SR packet was
	// generated.
	PacketCount uint32
	// The total number of payload octets (i.e., not including header or
	// padding) transmitted in RTP data packets by the sender since
	// starting transmission up until the time this SR packet was
	// generated.
	OctetCount uint32
	// Zero or more reception report blocks depending on the number of other
	// sources heard by this sender since the last report. Each reception report
	// block conveys statistics on the reception of RTP packets from a
	// single synchronization source.
	Reports []ReceptionReport
	// ProfileExtensions contains additional, payload-specific information that needs to
	// be reported regularly about the sender.
	ProfileExtensions []byte
}

const (
	srHeaderLength      = 24
	srSSRCOffset        = 0
	srNTPOffset         = srSSRCOffset + ssrcLength
	ntpTimeLength       = 8
	srRTPOffset         = srNTPOffset + ntpTimeLength
	rtpTimeLength       = 4
	srPacketCountOffset = srRTPOffset + rtpTimeLength
	srPacketCountLength = 4
	srOctetCountOffset  = srPacketCountOffset + srPacketCountLength
	srOctetCountLength  = 4
	srReportOffset      = srOctetCountOffset + srOctetCountLength
)

// Marshal encodes the SenderReport in binary.
func (r SenderReport) Marshal() ([]byte, error) {
	/*
	 *         0                   1                   2                   3
	 *         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * header |V=2|P|    RC   |   PT=SR=200   |             length            |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                         SSRC of sender                        |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * sender |              NTP timestamp, most significant word             |
	 * info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |             NTP timestamp, least significant word             |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                         RTP timestamp                         |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                     sender's packet count                     |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                      sender's octet count                     |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * report |                 SSRC_1 (SSRC of first source)                 |
	 * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *   1    | fraction lost |       cumulative number of packets lost       |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |           extended highest sequence number received           |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                      interarrival jitter                      |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                         last SR (LSR)                         |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                   delay since last SR (DLSR)                  |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * report |                 SSRC_2 (SSRC of second source)                |
	 * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *   2    :                               ...                             :
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 *        |                  profile-specific extensions                  |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	rawPacket := make([]byte, r.MarshalSize())
	packetBody := rawPacket[headerLength:]

	binary.BigEndian.PutUint32(packetBody[srSSRCOffset:], r.SSRC)
	binary.BigEndian.PutUint64(packetBody[srNTPOffset:], r.NTPTime)
	binary.BigEndian.PutUint32(packetBody[srRTPOffset:], r.RTPTime)
	binary.BigEndian.PutUint32(packetBody[srPacketCountOffset:], r.PacketCount)
	binary.BigEndian.PutUint32(packetBody[srOctetCountOffset:], r.OctetCount)

	offset := srHeaderLength
	for _, rp := range r.Reports {
		data, err := rp.Marshal()
		if err != nil {
			return nil, err
		}
		copy(packetBody[offset:], data)
		offset += receptionReportLength
	}

	if len(r.Reports) > countMax {
		return nil, errTooManyReports
	}

	copy(packetBody[offset:], r.ProfileExtensions)

	hData, err := r.Header().Marshal()
	if err != nil {
		return nil, err
	}
	copy(rawPacket, hData)

	return rawPacket, nil
}

// Unmarshal decodes the SenderReport from binary.
func (r *SenderReport) Unmarshal(rawPacket []byte) error {
	/*
	 *         0                   1                   2                   3
	 *         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * header |V=2|P|    RC   |   PT=SR=200   |             length            |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                         SSRC of sender                        |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * sender |              NTP timestamp, most significant word             |
	 * info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |             NTP timestamp, least significant word             |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                         RTP timestamp                         |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                     sender's packet count                     |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                      sender's octet count                     |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * report |                 SSRC_1 (SSRC of first source)                 |
	 * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *   1    | fraction lost |       cumulative number of packets lost       |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |           extended highest sequence number received           |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                      interarrival jitter                      |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                         last SR (LSR)                         |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                   delay since last SR (DLSR)                  |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * report |                 SSRC_2 (SSRC of second source)                |
	 * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *   2    :                               ...                             :
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 *        |                  profile-specific extensions                  |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	if len(rawPacket) < (headerLength + srHeaderLength) {
		return errPacketTooShort
	}

	var header Header
	if err := header.Unmarshal(rawPacket); err != nil {
		return err
	}

	if header.Type != TypeSenderReport {
		return errWrongType
	}

	packetBody := rawPacket[headerLength:]

	r.SSRC = binary.BigEndian.Uint32(packetBody[srSSRCOffset:])
	r.NTPTime = binary.BigEndian.Uint64(packetBody[srNTPOffset:])
	r.RTPTime = binary.BigEndian.Uint32(packetBody[srRTPOffset:])
	r.PacketCount = binary.BigEndian.Uint32(packetBody[srPacketCountOffset:])
	r.OctetCount = binary.BigEndian.Uint32(packetBody[srOctetCountOffset:])

	offset := srReportOffset
	for i := 0; i < int(header.Count); i++ {
		rrEnd := offset + receptionReportLength
		if rrEnd > len(packetBody) {
			return errPacketTooShort
		}
		rrBody := packetBody[offset : offset+receptionReportLength]
		offset = rrEnd

		var rr ReceptionReport
		if err := rr.Unmarshal(rrBody); err != nil {
			return err
		}
		r.Reports = append(r.Reports, rr)
	}

	if offset < len(packetBody) {
		r.ProfileExtensions = packetBody[offset:]
	}

	if uint8(len(r.Reports)) != header.Count { //nolint:gosec // G115
		return errInvalidHeader
	}

	return nil
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *SenderReport) DestinationSSRC() []uint32 {
	out := make([]uint32, len(r.Reports)+1)
	for i, v := range r.Reports {
		out[i] = v.SSRC
	}
	out[len(r.Reports)] = r.SSRC

	return out
}

// MarshalSize returns the size of the packet once marshaled.
func (r *SenderReport) MarshalSize() int {
	repsLength := 0
	for _, rep := range r.Reports {
		repsLength += rep.len()
	}

	return headerLength + srHeaderLength + repsLength + len(r.ProfileExtensions)
}

// Header returns the Header associated with this packet.
func (r *SenderReport) Header() Header {
	return Header{
		Count:  uint8(len(r.Reports)), //nolint:gosec // G115
		Type:   TypeSenderReport,
		Length: uint16((r.MarshalSize() / 4) - 1), //nolint:gosec // G115
	}
}

func (r SenderReport) String() string {
	var out strings.Builder
	fmt.Fprintf(&out, "SenderReport from %x\n", r.SSRC)
	fmt.Fprintf(&out, "\tNTPTime:\t%d\n", r.NTPTime)
	fmt.Fprintf(&out, "\tRTPTIme:\t%d\n", r.RTPTime)
	fmt.Fprintf(&out, "\tPacketCount:\t%d\n", r.PacketCount)
	fmt.Fprintf(&out, "\tOctetCount:\t%d\n", r.OctetCount)

	out.WriteString("\tSSRC    \tLost\tLastSequence\n")
	for _, i := range r.Reports {
		fmt.Fprintf(&out, "\t%x\t%d/%d\t%d\n", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber)
	}
	fmt.Fprintf(&out, "\tProfile Extension Data: %v\n", r.ProfileExtensions)

	return out.String()
}
