package rtsp

import (
	"bytes"
	"io"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"github.com/AlexxIT/go2rtc/pkg/core"
	"github.com/pion/rtcp"
	"github.com/pion/sdp/v3"
)

type RTCP struct {
	Channel byte
	Header  rtcp.Header
	Packets []rtcp.Packet
}

const sdpHeader = `v=0
o=- 0 0 IN IP4 0.0.0.0
s=-
t=0 0`

func UnmarshalSDP(rawSDP []byte) ([]*core.Media, error) {
	sd := &sdp.SessionDescription{}
	if err := sd.Unmarshal(rawSDP); err != nil {
		// fix multiple `s=` https://github.com/AlexxIT/WebRTC/issues/417
		rawSDP = regexp.MustCompile("\ns=[^\n]+").ReplaceAll(rawSDP, nil)

		// fix broken `c=` https://github.com/AlexxIT/go2rtc/issues/1426
		rawSDP = regexp.MustCompile("\nc=[^\n]+").ReplaceAll(rawSDP, nil)

		// fix SDP header for some cameras
		if i := bytes.Index(rawSDP, []byte("\nm=")); i > 0 {
			rawSDP = append([]byte(sdpHeader), rawSDP[i:]...)
		}

		// Fix invalid media type (errSDPInvalidValue) caused by
		// some TP-LINK IP camera, e.g. TL-IPC44GW
		for _, b := range regexp.MustCompile("m=[^ ]+ ").FindAll(rawSDP, -1) {
			switch string(b[2 : len(b)-1]) {
			case "audio", "video", "application":
			default:
				rawSDP = bytes.Replace(rawSDP, b, []byte("m=application "), 1)
			}
		}

		if err == io.EOF {
			rawSDP = append(rawSDP, '\n')
		}

		sd = &sdp.SessionDescription{}
		err = sd.Unmarshal(rawSDP)
		if err != nil {
			return nil, err
		}
	}

	// fix buggy camera https://github.com/AlexxIT/go2rtc/issues/771
	forceDirection := sd.Origin.Username == "CV-RTSPHandler"

	var medias []*core.Media

	for _, md := range sd.MediaDescriptions {
		media := core.UnmarshalMedia(md)

		// Check buggy SDP with fmtp for H264 on another track
		// https://github.com/AlexxIT/WebRTC/issues/419
		for _, codec := range media.Codecs {
			switch codec.Name {
			case core.CodecH264:
				if codec.FmtpLine == "" {
					codec.FmtpLine = findFmtpLine(codec.PayloadType, sd.MediaDescriptions)
				}
			case core.CodecH265:
				if codec.FmtpLine != "" {
					// all three parameters are needed for a valid fmtp line
					// https://github.com/AlexxIT/go2rtc/pull/1588
					if !strings.Contains(codec.FmtpLine, "sprop-vps=") ||
						!strings.Contains(codec.FmtpLine, "sprop-sps=") ||
						!strings.Contains(codec.FmtpLine, "sprop-pps=") {
						codec.FmtpLine = ""
					}
				}
			case core.CodecOpus:
				// fix OPUS for some cameras https://datatracker.ietf.org/doc/html/rfc7587
				codec.ClockRate = 48000
				codec.Channels = 2
			}
		}

		if media.Direction == "" || forceDirection {
			media.Direction = core.DirectionRecvonly
		}

		medias = append(medias, media)
	}

	return medias, nil
}

func findFmtpLine(payloadType uint8, descriptions []*sdp.MediaDescription) string {
	s := strconv.Itoa(int(payloadType))
	for _, md := range descriptions {
		codec := core.UnmarshalCodec(md, s)
		if codec.FmtpLine != "" {
			return codec.FmtpLine
		}
	}
	return ""
}

// urlParse fix bugs:
// 1. Content-Base: rtsp://::ffff:192.168.1.123/onvif/profile.1/
// 2. Content-Base: rtsp://rtsp://turret2-cam.lan:554/stream1/
// 3. Content-Base: 192.168.253.220:1935/
func urlParse(rawURL string) (*url.URL, error) {
	// fix https://github.com/AlexxIT/go2rtc/issues/830
	if strings.HasPrefix(rawURL, "rtsp://rtsp://") {
		rawURL = rawURL[7:]
	}

	// fix https://github.com/AlexxIT/go2rtc/issues/1852
	if !strings.Contains(rawURL, "://") {
		rawURL = "rtsp://" + rawURL
	}

	u, err := url.Parse(rawURL)
	if err != nil && strings.HasSuffix(err.Error(), "after host") {
		if i := indexN(rawURL, '/', 3); i > 0 {
			return urlParse(rawURL[:i] + ":" + rawURL[i:])
		}
	}

	return u, err
}

func indexN(s string, c byte, n int) int {
	var offset int
	for {
		i := strings.IndexByte(s[offset:], c)
		if i < 0 {
			break
		}
		if n--; n == 0 {
			return offset + i
		}
		offset += i + 1
	}
	return -1
}
