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

package sdp

import (
	"fmt"
	"net/url"
	"strconv"
	"time"
)

// Constants for SDP attributes used in JSEP.
const (
	AttrKeyCandidate        = "candidate"
	AttrKeyEndOfCandidates  = "end-of-candidates"
	AttrKeyIdentity         = "identity"
	AttrKeyGroup            = "group"
	AttrKeySSRC             = "ssrc"
	AttrKeySSRCGroup        = "ssrc-group"
	AttrKeyMsid             = "msid"
	AttrKeyMsidSemantic     = "msid-semantic"
	AttrKeyConnectionSetup  = "setup"
	AttrKeyMID              = "mid"
	AttrKeyICELite          = "ice-lite"
	AttrKeyICEOptions       = "ice-options"
	AttrKeyRTCPMux          = "rtcp-mux"
	AttrKeyRTCPRsize        = "rtcp-rsize"
	AttrKeyInactive         = "inactive"
	AttrKeyRecvOnly         = "recvonly"
	AttrKeySendOnly         = "sendonly"
	AttrKeySendRecv         = "sendrecv"
	AttrKeyExtMap           = "extmap"
	AttrKeyExtMapAllowMixed = "extmap-allow-mixed"
	AttrKeyCryptex          = "cryptex"
)

// Constants for semantic tokens used in JSEP.
const (
	SemanticTokenLipSynchronization     = "LS"
	SemanticTokenFlowIdentification     = "FID"
	SemanticTokenForwardErrorCorrection = "FEC"
	// https://datatracker.ietf.org/doc/html/rfc5956#section-4.1
	SemanticTokenForwardErrorCorrectionFramework = "FEC-FR"
	SemanticTokenWebRTCMediaStreams              = "WMS"
)

// Constants for extmap key.
const (
	ExtMapValueTransportCC = 3
)

func extMapURI() map[int]string {
	return map[int]string{
		ExtMapValueTransportCC: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01",
	}
}

// API to match draft-ietf-rtcweb-jsep
// Move to webrtc or its own package?

// NewJSEPSessionDescription creates a new SessionDescription with
// some settings that are required by the JSEP spec.
//
// Note: Since v2.4.0, session ID has been fixed to use crypto random according to
//
//	JSEP spec, so that NewJSEPSessionDescription now returns error as a second
//	return value.
func NewJSEPSessionDescription(identity bool) (*SessionDescription, error) {
	sid, err := newSessionID()
	if err != nil {
		return nil, err
	}
	descr := &SessionDescription{
		Version: 0,
		Origin: Origin{
			Username:       "-",
			SessionID:      sid,
			SessionVersion: uint64(time.Now().Unix()), //nolint:gosec // G115
			NetworkType:    "IN",
			AddressType:    "IP4",
			UnicastAddress: "0.0.0.0",
		},
		SessionName: "-",
		TimeDescriptions: []TimeDescription{
			{
				Timing: Timing{
					StartTime: 0,
					StopTime:  0,
				},
				RepeatTimes: nil,
			},
		},
		Attributes: []Attribute{
			// 	"Attribute(ice-options:trickle)", // TODO: implement trickle ICE
		},
	}

	if identity {
		descr.WithPropertyAttribute(AttrKeyIdentity)
	}

	return descr, nil
}

// WithPropertyAttribute adds a property attribute 'a=key' to the session description.
func (s *SessionDescription) WithPropertyAttribute(key string) *SessionDescription {
	s.Attributes = append(s.Attributes, NewPropertyAttribute(key))

	return s
}

// WithValueAttribute adds a value attribute 'a=key:value' to the session description.
func (s *SessionDescription) WithValueAttribute(key, value string) *SessionDescription {
	s.Attributes = append(s.Attributes, NewAttribute(key, value))

	return s
}

// addOrUpdateICEOption adds or updates the ice-options attribute with the given value.
func (s *SessionDescription) addOrUpdateICEOption(value string) *SessionDescription {
	for i := range s.Attributes {
		if s.Attributes[i].Key == AttrKeyICEOptions {
			prefix := " "
			if s.Attributes[i].Value == "" {
				prefix = ""
			}

			s.Attributes[i].Value += prefix + value

			return s
		}
	}

	return s.WithValueAttribute(AttrKeyICEOptions, value)
}

// WithICETrickleAdvertised advertises ICE trickle support in the session description.
// See https://datatracker.ietf.org/doc/html/rfc9429#section-5.2.1
func (s *SessionDescription) WithICETrickleAdvertised() *SessionDescription {
	return s.addOrUpdateICEOption("trickle")
}

// WithICERenomination advertises ICE renomination support in the session description.
// See https://datatracker.ietf.org/doc/html/draft-thatcher-ice-renomination-01#section-3
func (s *SessionDescription) WithICERenomination() *SessionDescription {
	return s.addOrUpdateICEOption("renomination")
}

// WithFingerprint adds a fingerprint to the session description.
func (s *SessionDescription) WithFingerprint(algorithm, value string) *SessionDescription {
	return s.WithValueAttribute("fingerprint", algorithm+" "+value)
}

// WithMedia adds a media description to the session description.
func (s *SessionDescription) WithMedia(md *MediaDescription) *SessionDescription {
	s.MediaDescriptions = append(s.MediaDescriptions, md)

	return s
}

// NewJSEPMediaDescription creates a new MediaName with
// some settings that are required by the JSEP spec.
func NewJSEPMediaDescription(codecType string, _ []string) *MediaDescription {
	return &MediaDescription{
		MediaName: MediaName{
			Media:  codecType,
			Port:   RangedPort{Value: 9},
			Protos: []string{"UDP", "TLS", "RTP", "SAVPF"},
		},
		ConnectionInformation: &ConnectionInformation{
			NetworkType: "IN",
			AddressType: "IP4",
			Address: &Address{
				Address: "0.0.0.0",
			},
		},
	}
}

// WithPropertyAttribute adds a property attribute 'a=key' to the media description.
func (d *MediaDescription) WithPropertyAttribute(key string) *MediaDescription {
	d.Attributes = append(d.Attributes, NewPropertyAttribute(key))

	return d
}

// WithValueAttribute adds a value attribute 'a=key:value' to the media description.
func (d *MediaDescription) WithValueAttribute(key, value string) *MediaDescription {
	d.Attributes = append(d.Attributes, NewAttribute(key, value))

	return d
}

// WithFingerprint adds a fingerprint to the media description.
func (d *MediaDescription) WithFingerprint(algorithm, value string) *MediaDescription {
	return d.WithValueAttribute("fingerprint", algorithm+" "+value)
}

// WithICECredentials adds ICE credentials to the media description.
func (d *MediaDescription) WithICECredentials(username, password string) *MediaDescription {
	return d.
		WithValueAttribute("ice-ufrag", username).
		WithValueAttribute("ice-pwd", password)
}

// WithCodec adds codec information to the media description.
func (d *MediaDescription) WithCodec(
	payloadType uint8,
	name string,
	clockrate uint32,
	channels uint16,
	fmtp string,
) *MediaDescription {
	d.MediaName.Formats = append(d.MediaName.Formats, strconv.Itoa(int(payloadType)))
	rtpmap := fmt.Sprintf("%d %s/%d", payloadType, name, clockrate)
	if channels > 0 {
		rtpmap += fmt.Sprintf("/%d", channels)
	}
	d.WithValueAttribute("rtpmap", rtpmap)
	if fmtp != "" {
		d.WithValueAttribute("fmtp", fmt.Sprintf("%d %s", payloadType, fmtp))
	}

	return d
}

// WithMediaSource adds media source information to the media description.
func (d *MediaDescription) WithMediaSource(ssrc uint32, cname, streamLabel, label string) *MediaDescription {
	return d.
		WithValueAttribute("ssrc", fmt.Sprintf("%d cname:%s", ssrc, cname)). // Deprecated but not phased out?
		WithValueAttribute("ssrc", fmt.Sprintf("%d msid:%s %s", ssrc, streamLabel, label)).
		WithValueAttribute("ssrc", fmt.Sprintf("%d mslabel:%s", ssrc, streamLabel)). // Deprecated but not phased out?
		WithValueAttribute("ssrc", fmt.Sprintf("%d label:%s", ssrc, label))          // Deprecated but not phased out?
}

// WithCandidate adds an ICE candidate to the media description.
// Deprecated: use WithICECandidate instead.
func (d *MediaDescription) WithCandidate(value string) *MediaDescription {
	return d.WithValueAttribute("candidate", value)
}

// WithExtMap adds an extmap to the media description.
func (d *MediaDescription) WithExtMap(e ExtMap) *MediaDescription {
	return d.WithPropertyAttribute(e.Marshal())
}

// WithTransportCCExtMap adds an extmap to the media description.
func (d *MediaDescription) WithTransportCCExtMap() *MediaDescription {
	uri, _ := url.Parse(extMapURI()[ExtMapValueTransportCC])
	e := ExtMap{
		Value: ExtMapValueTransportCC,
		URI:   uri,
	}

	return d.WithExtMap(e)
}
