// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package interceptor

import (
	"github.com/pion/interceptor"
	"github.com/pion/rtp"
	"github.com/pion/sdp/v3"
	"github.com/pion/webrtc/v4"
	"go.uber.org/zap/zapcore"

	"github.com/livekit/livekit-server/pkg/sfu/utils"
	"github.com/livekit/protocol/logger"
)

const (
	simulcastProbeCount = 10
)

type SimulcastTrackInfo struct {
	Mid            string
	StreamID       string
	RepairSSRC     uint32 // set only when `IsRepairStream: false`, i. e. RTX SSRC for the primary stream
	IsRepairStream bool
}

func (s *SimulcastTrackInfo) MarshalLogObject(e zapcore.ObjectEncoder) error {
	e.AddString("Mid", s.Mid)
	e.AddString("StreamID", s.StreamID)
	e.AddUint32("RepairSSRC", s.RepairSSRC)
	e.AddBool("IsRepairStream", s.IsRepairStream)
	return nil
}

// -------------------------------------------------------------------

type UnhandleSimulcastOption func(u *UnhandleSimulcastInterceptor) error

func UnhandleSimulcastTracks(logger logger.Logger, tracks map[uint32]SimulcastTrackInfo) UnhandleSimulcastOption {
	return func(u *UnhandleSimulcastInterceptor) error {
		u.logger = logger
		u.simTracks = tracks
		return nil
	}
}

type UnhandleSimulcastInterceptorFactory struct {
	opts []UnhandleSimulcastOption
}

func (f *UnhandleSimulcastInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
	i := &UnhandleSimulcastInterceptor{simTracks: map[uint32]SimulcastTrackInfo{}}
	for _, o := range f.opts {
		if err := o(i); err != nil {
			return nil, err
		}
	}
	return i, nil
}

func NewUnhandleSimulcastInterceptorFactory(opts ...UnhandleSimulcastOption) (*UnhandleSimulcastInterceptorFactory, error) {
	return &UnhandleSimulcastInterceptorFactory{opts: opts}, nil
}

type unhandleSimulcastRTPReader struct {
	SimulcastTrackInfo
	logger    logger.Logger
	tryTimes  int
	reader    interceptor.RTPReader
	midExtID  uint8
	ridExtID  uint8
	rsidExtID uint8
}

func (u *unhandleSimulcastRTPReader) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
	n, a, err := u.reader.Read(b, a)
	if u.tryTimes < 0 || err != nil {
		return n, a, err
	}

	header := rtp.Header{}
	hsize, err := header.Unmarshal(b[:n])
	if err != nil {
		return n, a, nil
	}
	var mid, rid, rsid string
	if payload := header.GetExtension(u.midExtID); payload != nil {
		mid = string(payload)
	}

	if payload := header.GetExtension(u.ridExtID); payload != nil {
		rid = string(payload)
	}

	if payload := header.GetExtension(u.rsidExtID); payload != nil {
		rid = string(payload)
	}

	if mid != "" && (rid != "" || rsid != "") {
		u.logger.Debugw(
			"unhandle stream found",
			"mid", mid,
			"rid", rid,
			"rsid", rsid,
			"ssrc", header.SSRC,
			"simulcastTrackInfo", u.SimulcastTrackInfo,
		)
		u.tryTimes = -1
		return n, a, nil
	} else {
		// ignore padding only packet for probe count
		if !header.Padding || n-header.MarshalSize()-int(b[n-1]) != 0 {
			u.tryTimes--
		}
	}

	if mid == "" {
		header.SetExtension(u.midExtID, []byte(u.Mid))
	}
	if rid == "" && !u.IsRepairStream {
		header.SetExtension(u.ridExtID, []byte(u.StreamID))
	}
	if rsid == "" && u.IsRepairStream {
		header.SetExtension(u.rsidExtID, []byte(u.StreamID))
	}

	hsize2 := header.MarshalSize()

	if hsize2-hsize+n > len(b) { // no enough buf to set extension
		return n, a, nil
	}
	copy(b[hsize2:], b[hsize:n])
	header.MarshalTo(b)
	u.logger.Debugw(
		"unhandle stream injecting",
		"mid", mid,
		"rid", rid,
		"rsid", rsid,
		"ssrc", header.SSRC,
		"simulcastTrackInfo", u.SimulcastTrackInfo,
	)
	return hsize2 - hsize + n, a, nil
}

type UnhandleSimulcastInterceptor struct {
	interceptor.NoOp
	logger    logger.Logger
	simTracks map[uint32]SimulcastTrackInfo
}

func (u *UnhandleSimulcastInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
	if t, ok := u.simTracks[info.SSRC]; ok {
		midExtensionID := utils.GetHeaderExtensionID(info.RTPHeaderExtensions, webrtc.RTPHeaderExtensionCapability{URI: sdp.SDESMidURI})
		streamIDExtensionID := utils.GetHeaderExtensionID(info.RTPHeaderExtensions, webrtc.RTPHeaderExtensionCapability{URI: sdp.SDESRTPStreamIDURI})
		repairStreamIDExtensionID := utils.GetHeaderExtensionID(info.RTPHeaderExtensions, webrtc.RTPHeaderExtensionCapability{URI: sdp.SDESRepairRTPStreamIDURI})
		if midExtensionID == 0 || streamIDExtensionID == 0 || repairStreamIDExtensionID == 0 {
			return reader
		}

		return &unhandleSimulcastRTPReader{
			SimulcastTrackInfo: t,
			logger:             u.logger,
			reader:             reader,
			tryTimes:           simulcastProbeCount,
			midExtID:           uint8(midExtensionID),
			ridExtID:           uint8(streamIDExtensionID),
			rsidExtID:          uint8(repairStreamIDExtensionID),
		}
	}
	return reader
}
