package sip

import (
	"strings"
	"testing"

	"github.com/emiago/sipgo/sip"
)

const (
	host                        = "localhost"
	expectedMethod              = sip.OPTIONS
	expectedMaxForwardsHeader   = "Max-Forwards: 70"
	expectedUserAgent           = "Jitsi2.10.5550Mac OS X"
	expectedAccept              = "application/sdp"
	expectedProtocolName        = "SIP"
	expectedProtocolVersion     = "2.0"
	expectedTransport           = "UDP"
	expectedCSeqHeader          = "CSeq: 1 OPTIONS"
	expectedPresenceHeaderValue = "presence"
)

func TestNewSipRequest(t *testing.T) {
	t.Run("returns ok to false if method is zero", func(t *testing.T) {
		req, ok := NewSipRequest("", "", nil)
		if ok {
			t.Fatal("got true ok")
		}
		if req != nil {
			t.Fatal("got non 'nil' request")
		}
	})
	t.Run("returns ok to false if host is zero", func(t *testing.T) {
		req, ok := NewSipRequest(sip.OPTIONS, "", nil)
		if ok {
			t.Fatal("got true ok")
		}
		if req != nil {
			t.Fatal("got non 'nil' request")
		}
	})
	t.Run("returns the request with default options", func(t *testing.T) {
		req, ok := NewSipRequest(sip.OPTIONS, host, nil)
		if !ok {
			t.Fatal("got false ok")
		}
		if req == nil {
			t.Fatal("got 'nil' request")
		}
		if req.Method != expectedMethod {
			t.Fatalf("got method %s, want %s", req.Method, expectedMethod)
		}
		if req.Recipient.Host != host {
			t.Fatalf("got recipient host %s, want %s", req.Recipient.Host, host)
		}
		if req.Recipient.Port != 0 {
			t.Fatalf("got recipient port %d, want 0", req.Recipient.Port)
		}
		transport := req.Transport()
		if transport != "UDP" {
			t.Fatalf("got recipient transport %s, wants UDP", transport)
		}
		startLine := req.StartLine()
		expectedStartLine := "OPTIONS sip:localhost SIP/2.0"
		if startLine != expectedStartLine {
			t.Fatalf("got start line %s, want %s", startLine, expectedStartLine)
		}
		if len(req.Headers()) != 9 {
			t.Fatalf("got %d headers, want 9", len(req.Headers()))
		}
		viaHeader := req.Via()
		if viaHeader == nil {
			t.Fatal("got 'nil' Via header")
		}
		if viaHeader.ProtocolName != expectedProtocolName {
			t.Fatalf("got protocol name %s, want %s", viaHeader.ProtocolName, expectedProtocolName)
		}
		if viaHeader.ProtocolVersion != expectedProtocolVersion {
			t.Fatalf("got protocol version %s, want %s", viaHeader.ProtocolVersion, expectedProtocolVersion)
		}
		expectedTransport := expectedTransport
		if viaHeader.Transport != expectedTransport {
			t.Fatalf("got transport %s, want %s", viaHeader.Transport, expectedTransport)
		}
		if viaHeader.Host != host {
			t.Fatalf("got host %s, want %s", viaHeader.Host, host)
		}
		expectedPort := 0
		if viaHeader.Port != expectedPort {
			t.Fatalf("got port %d, want %d", viaHeader.Port, expectedPort)
		}
		if len(viaHeader.Params) != 2 {
			t.Fatalf("got %d parameters, want 2", len(viaHeader.Params))
		}
		if viaHeader.Params["branch"] == "" {
			t.Fatal("got zero branch parameter")
		}
		if viaHeader.Params["rport"] != "" {
			t.Fatal("got non-zero rport parameter")
		}
		maxForwardsHeader := req.MaxForwards().String()
		if maxForwardsHeader != expectedMaxForwardsHeader {
			t.Fatalf("got Max-Forwards header %s, want %s", maxForwardsHeader, expectedMaxForwardsHeader)
		}
		fromHeader := req.From().String()
		expectedFromHeader := "From: <sip:localhost>;tag="
		if strings.Contains(fromHeader, expectedFromHeader) == false {
			t.Fatalf("got From header %s, want %s", fromHeader, expectedFromHeader)
		}
		toHeader := req.To().String()
		expectedToHeader := "To: <sip:localhost>"
		if toHeader != expectedToHeader {
			t.Fatalf("got To header %s, want %s", toHeader, expectedToHeader)
		}
		ciHeader := req.CallID().String()
		if ciHeader == "" {
			t.Fatal("got zero Call-ID header")
		}
		cseqHeader := req.CSeq().String()
		if cseqHeader != expectedCSeqHeader {
			t.Fatalf("got CSeq header %s, want %s", cseqHeader, expectedCSeqHeader)
		}
		userAgentHeader := req.GetHeader("user-agent")
		if userAgentHeader == nil {
			t.Fatal("got 'nil' User-Agent header")
		}
		if userAgentHeader.Value() != expectedUserAgent {
			t.Fatalf("got User-Agent header %s, want %s", userAgentHeader.Value(), expectedUserAgent)
		}
		acceptHeader := req.GetHeader("accept")
		if acceptHeader == nil {
			t.Fatal("got 'nil' Accept header")
		}
		if acceptHeader.Value() != expectedAccept {
			t.Fatalf("got Accept header %s, want %s", acceptHeader.Value(), expectedAccept)
		}
		if req.Contact() != nil {
			t.Fatal("got non-nil Contact header")
		}
		if req.ContentType() != nil {
			t.Fatal("got non-nil Content-Type header")
		}
		if req.ContentLength() == nil {
			t.Fatal("got nil Content-Length header")
		}
		contentLenValue := req.ContentLength().Value()
		if contentLenValue != "0" {
			t.Fatalf("got Content-Length header %s, want 0", contentLenValue)
		}
		if req.Body() != nil {
			t.Fatal("got non-nil body")
		}
	})
	t.Run("returns the request with custom options", func(t *testing.T) {
		opts := NewSipRequestOpts{
			Port:      5061,
			LocalHost: "192.168.1.1",
			LocalPort: 5065,
			ToUser:    "dembele",
			User:      "fabian",
			Password:  "pass-0",
			Transport: TCP,
		}
		req, ok := NewSipRequest(sip.OPTIONS, host, &opts)
		if !ok {
			t.Fatal("got false ok")
		}
		if req == nil {
			t.Fatal("got 'nil' request")
		}
		if req.Method != expectedMethod {
			t.Fatalf("got method %s, want %s", req.Method, expectedMethod)
		}
		if req.Recipient.Host != host {
			t.Fatalf("got recipient host %s, want %s", req.Recipient.Host, host)
		}
		if req.Recipient.Port != opts.Port {
			t.Fatalf("got recipient port %d, want %d", req.Recipient.Port, opts.Port)
		}
		transport := req.Transport()
		if transport != TCP.String() {
			t.Fatalf("got recipient transport %s, wants %s", transport, TCP.String())
		}
		startLine := req.StartLine()
		expectedStartLine := "OPTIONS sip:dembele@localhost:5061 SIP/2.0"
		if startLine != expectedStartLine {
			t.Fatalf("got start line %s, want %s", startLine, expectedStartLine)
		}
		if len(req.Headers()) != 9 {
			t.Fatalf("got %d headers, want 9", len(req.Headers()))
		}
		viaHeader := req.Via()
		if viaHeader == nil {
			t.Fatal("got 'nil' Via header")
		}
		if viaHeader.ProtocolName != expectedProtocolName {
			t.Fatalf("got protocol name %s, want %s", viaHeader.ProtocolName, expectedProtocolName)
		}
		if viaHeader.ProtocolVersion != expectedProtocolVersion {
			t.Fatalf("got protocol version %s, want %s", viaHeader.ProtocolVersion, expectedProtocolVersion)
		}
		expectedTransport := "TCP"
		if viaHeader.Transport != expectedTransport {
			t.Fatalf("got transport %s, want %s", viaHeader.Transport, expectedTransport)
		}
		if viaHeader.Host != opts.LocalHost {
			t.Fatalf("got host %s, want %s", viaHeader.Host, opts.LocalHost)
		}
		if viaHeader.Port != opts.LocalPort {
			t.Fatalf("got port %d, want %d", viaHeader.Port, opts.LocalPort)
		}
		if len(viaHeader.Params) != 2 {
			t.Fatalf("got %d parameters, want 2", len(viaHeader.Params))
		}
		if viaHeader.Params["branch"] == "" {
			t.Fatal("got zero branch parameter")
		}
		if viaHeader.Params["rport"] != "" {
			t.Fatal("got non-zero rport parameter")
		}
		maxForwardsHeader := req.MaxForwards().String()
		if maxForwardsHeader != expectedMaxForwardsHeader {
			t.Fatalf("got Max-Forwards header %s, want %s", maxForwardsHeader, expectedMaxForwardsHeader)
		}
		fromHeader := req.From().String()
		expectedFromHeader := "From: <sip:fabian:pass-0@192.168.1.1:5065>;tag="
		if !strings.Contains(fromHeader, expectedFromHeader) {
			t.Fatalf("got From header %s, want %s", fromHeader, expectedFromHeader)
		}
		toHeader := req.To().String()
		expectedToHeader := "To: <sip:dembele@localhost:5061>"
		if toHeader != expectedToHeader {
			t.Fatalf("got To header %s, want %s", toHeader, expectedToHeader)
		}
		ciHeader := req.CallID().String()
		if ciHeader == "" {
			t.Fatal("got zero Call-ID header")
		}
		cseqHeader := req.CSeq().String()
		if cseqHeader != expectedCSeqHeader {
			t.Fatalf("got CSeq header %s, want %s", cseqHeader, expectedCSeqHeader)
		}
		userAgentHeader := req.GetHeader("user-agent")
		if userAgentHeader == nil {
			t.Fatal("got 'nil' User-Agent header")
		}
		if userAgentHeader.Value() != expectedUserAgent {
			t.Fatalf("got User-Agent header %s, want %s", userAgentHeader.Value(), expectedUserAgent)
		}
		acceptHeader := req.GetHeader("accept")
		if acceptHeader == nil {
			t.Fatal("got 'nil' Accept header")
		}
		if acceptHeader.Value() != expectedAccept {
			t.Fatalf("got Accept header %s, want %s", acceptHeader.Value(), expectedAccept)
		}
		if req.Contact() != nil {
			t.Fatal("got non-nil Contact header")
		}
		if req.ContentType() != nil {
			t.Fatal("got non-nil Content-Type header")
		}
		if req.ContentLength() == nil {
			t.Fatal("got nil Content-Length header")
		}
		contentLenValue := req.ContentLength().Value()
		if contentLenValue != "0" {
			t.Fatalf("got Content-Length header %s, want 0", contentLenValue)
		}
		if req.Body() != nil {
			t.Fatal("got non-nil body")
		}
	})
	t.Run("adds contact header for methods that require it", func(t *testing.T) {
		req, ok := NewSipRequest(sip.REGISTER, "localhost", nil)
		if !ok {
			t.Fatal("got false ok")
		}
		if req == nil {
			t.Fatal("got 'nil' request")
		}
		if req.Contact() == nil {
			t.Fatal("got 'nil' Contact header")
		}
		expectedContact := "<sip:localhost>;transport=UDP"
		if req.Contact().Value() != expectedContact {
			t.Fatalf("got Contact header %s, want %s", req.Contact().Value(), expectedContact)
		}
	})
	t.Run("adds content type and body for methods that require them", func(t *testing.T) {
		req, ok := NewSipRequest(sip.INVITE, "localhost", nil)
		if !ok {
			t.Fatal("got false ok")
		}
		if req == nil {
			t.Fatal("got 'nil' request")
		}
		if req.ContentType() == nil {
			t.Fatal("got 'nil' Content-Type header")
		}
		expectedContentType := "application/sdp"
		if req.ContentType().Value() != expectedContentType {
			t.Fatalf("got Content-Type header %s, want %s", req.ContentType().Value(), expectedContentType)
		}
		if req.Body() == nil {
			t.Fatal("got 'nil' body")
		}
		expectedBody := "o=caller"
		if !strings.Contains(string(req.Body()), expectedBody) {
			t.Fatalf("got body %s, want %s", req.Body(), expectedBody)
		}
	})
}

//gocognit:ignore
func TestNewViaHeader(t *testing.T) {
	t.Run("returns the header with default options", func(t *testing.T) {
		header, ok := NewViaHeader(nil)
		if !ok {
			t.Fatal("got false ok")
		}
		if header == nil {
			t.Fatal("got 'nil' Via header")
		}
		expectedProtocolName := "SIP"
		if header.ProtocolName != expectedProtocolName {
			t.Fatalf("got protocol name %s, want %s", header.ProtocolName, expectedProtocolName)
		}
		expectedProtocolVersion := "2.0"
		if header.ProtocolVersion != expectedProtocolVersion {
			t.Fatalf("got protocol version %s, want %s", header.ProtocolVersion, expectedProtocolVersion)
		}
		expectedTransport := "UDP"
		if header.Transport != expectedTransport {
			t.Fatalf("got transport %s, want %s", header.Transport, expectedTransport)
		}
		if header.Host != host {
			t.Fatalf("got host %s, want %s", header.Host, host)
		}
		expectedPort := 0
		if header.Port != expectedPort {
			t.Fatalf("got port %d, want %d", header.Port, expectedPort)
		}
		if len(header.Params) != 2 {
			t.Fatalf("got %d parameters, want 2", len(header.Params))
		}
		if header.Params["branch"] == "" {
			t.Fatal("got zero branch parameter")
		}
		if header.Params["rport"] != "" {
			t.Fatal("got non-zero rport parameter")
		}
	})
	t.Run("returns the header with custom options", func(t *testing.T) {
		opts := NewViaOpts{
			ProtocolName:    "PNAME",
			ProtocolVersion: "1.0",
			Transport:       "TCP",
			Host:            "localhost",
			Port:            5061,
		}
		header, ok := NewViaHeader(&opts)
		if !ok {
			t.Fatal("got false ok")
		}
		if header == nil {
			t.Fatal("got 'nil' header")
		}
		if header.ProtocolName != opts.ProtocolName {
			t.Fatalf("got protocol name %s, want %s", header.ProtocolName, opts.ProtocolName)
		}
		if header.ProtocolVersion != opts.ProtocolVersion {
			t.Fatalf("got protocol version %s, want %s", header.ProtocolVersion, opts.ProtocolVersion)
		}
		if header.Transport != "TCP" {
			t.Fatalf("got transport %s, want %s", header.Transport, "TCP")
		}
		if header.Host != opts.Host {
			t.Fatalf("got host %s, want %s", header.Host, opts.Host)
		}
		if header.Port != opts.Port {
			t.Fatalf("got port %d, want %d", header.Port, opts.Port)
		}
		if len(header.Params) != 2 {
			t.Fatalf("got %d parameters, want 2", len(header.Params))
		}
		if header.Params["branch"] == "" {
			t.Fatal("got zero branch parameter")
		}
		if header.Params["rport"] != "" {
			t.Fatal("got non-zero rport parameter")
		}
	})
}

func TestIsContactRequired(t *testing.T) {
	tests := []struct {
		name     string
		method   sip.RequestMethod
		expected bool
	}{
		{sip.INVITE.String(), sip.INVITE, true},
		{sip.ACK.String(), sip.ACK, false},
		{sip.CANCEL.String(), sip.CANCEL, false},
		{sip.BYE.String(), sip.BYE, false},
		{sip.REGISTER.String(), sip.REGISTER, true},
		{sip.OPTIONS.String(), sip.OPTIONS, false},
		{sip.SUBSCRIBE.String(), sip.SUBSCRIBE, true},
		{sip.NOTIFY.String(), sip.NOTIFY, true},
		{sip.REFER.String(), sip.REFER, true},
		{sip.INFO.String(), sip.INFO, false},
		{sip.MESSAGE.String(), sip.MESSAGE, false},
		{sip.PRACK.String(), sip.PRACK, false},
		{sip.UPDATE.String(), sip.UPDATE, false},
		{sip.PUBLISH.String(), sip.PUBLISH, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := IsContactRequired(tt.method); got != tt.expected {
				t.Fatalf("got %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestAddMethodHeaders(t *testing.T) {
	t.Run("returns ok to false if request is nil", func(t *testing.T) {
		ok := AddMethodHeaders(nil, sip.OPTIONS)
		if ok {
			t.Fatal("got true ok")
		}
	})
	t.Run("OPTIONS", func(t *testing.T) {
		req := sip.Request{}
		ok := AddMethodHeaders(&req, sip.OPTIONS)
		if !ok {
			t.Fatal("got false ok")
		}
		headers := req.Headers()
		if len(headers) != 1 {
			t.Fatalf("got %d headers, want 1", len(headers))
		}
		header := req.GetHeader("accept")
		if header == nil {
			t.Fatal("got 'nil' accept header")
		}
		value := header.Value()
		if value != "application/sdp" {
			t.Fatalf("got accept header value %v, want application/sdp", value)
		}
	})
	t.Run("REGISTER", func(t *testing.T) {
		req := sip.Request{}
		ok := AddMethodHeaders(&req, sip.REGISTER)
		if !ok {
			t.Fatal("got false ok")
		}
		headers := req.Headers()
		if len(headers) != 1 {
			t.Fatalf("got %d headers, want 1", len(headers))
		}
		header := req.GetHeader("expires")
		if header == nil {
			t.Fatal("got 'nil' expires header")
		}
		value := header.Value()
		if value != "3600" {
			t.Fatalf("got expires header value %v, want 3600", value)
		}
	})
	t.Run("PRACK", func(t *testing.T) {
		req := sip.Request{}
		ok := AddMethodHeaders(&req, sip.PRACK)
		if !ok {
			t.Fatal("got false ok")
		}
		headers := req.Headers()
		if len(headers) != 1 {
			t.Fatalf("got %d headers, want 1", len(headers))
		}
		header := req.GetHeader("RAck")
		if header == nil {
			t.Fatal("got 'nil' rack header")
		}
		value := header.Value()
		if value != "1 314159 INVITE" {
			t.Fatalf("got rack header value %v, want 1 314159 INVITE", value)
		}
	})
	t.Run("SUBSCRIBE", func(t *testing.T) {
		req := sip.Request{}
		ok := AddMethodHeaders(&req, sip.SUBSCRIBE)
		if !ok {
			t.Fatal("got false ok")
		}
		headers := req.Headers()
		if len(headers) != 3 {
			t.Fatalf("got %d headers, want 3", len(headers))
		}
		header := req.GetHeader("event")
		if header == nil {
			t.Fatal("got 'nil' event header")
		}
		value := header.Value()
		if value != expectedPresenceHeaderValue {
			t.Fatalf("got event header value %v, want presence", value)
		}
		header = req.GetHeader("accept")
		if header == nil {
			t.Fatal("got 'nil' accept header")
		}
		value = header.Value()
		if value != "application/pidf+xml" {
			t.Fatalf("got accept header value %v, want application/pidf+xml", value)
		}
		header = req.GetHeader("supported")
		if header == nil {
			t.Fatal("got 'nil' supported header")
		}
		value = header.Value()
		if value != "eventlist" {
			t.Fatalf("got supported header value %v, want eventlist", value)
		}
	})
	t.Run("NOTIFY", func(t *testing.T) {
		req := sip.Request{}
		ok := AddMethodHeaders(&req, sip.NOTIFY)
		if !ok {
			t.Fatal("got false ok")
		}
		headers := req.Headers()
		if len(headers) != 2 {
			t.Fatalf("got %d headers, want 2", len(headers))
		}
		header := req.GetHeader("event")
		if header == nil {
			t.Fatal("got 'nil' event header")
		}
		value := header.Value()
		if value != expectedPresenceHeaderValue {
			t.Fatalf("got event header value %v, want presence", value)
		}
		header = req.GetHeader("subscription-state")
		if header == nil {
			t.Fatal("got 'nil' subscription-state header")
		}
		value = header.Value()
		if value != "active;expires=3500" {
			t.Fatalf("got subscription-state header value %v, want active;expires=3500", value)
		}
	})
	t.Run("REFER returns error if call id is not set", func(t *testing.T) {
		req := sip.Request{}
		ok := AddMethodHeaders(&req, sip.REFER)
		if ok {
			t.Fatal("got true ok")
		}
		headers := req.Headers()
		if len(headers) != 0 {
			t.Fatalf("got %d headers, want 0", len(headers))
		}
	})
	t.Run("REFER", func(t *testing.T) {
		req := sip.Request{
			Recipient: sip.Uri{
				Host: "127.0.0.1",
			},
		}
		ciHeader := sip.CallIDHeader("123456")
		req.AppendHeader(&ciHeader)
		ok := AddMethodHeaders(&req, sip.REFER)
		if !ok {
			t.Fatal("got false ok")
		}
		headers := req.Headers()
		if len(headers) != 4 {
			t.Fatalf("got %d headers, want 3", len(headers))
		}
		header := req.GetHeader("refer-to")
		if header == nil {
			t.Fatal("got 'nil' refer-to header")
		}
		value := header.Value()
		expectedValue := "<sip:carol@127.0.0.1;Replaces=123456%3Bto-tag%3D54321%3Bfrom-tag%3D12345>"
		if value != expectedValue {
			t.Fatalf("got refer-to header value %s, want %s", value, expectedValue)
		}
		header = req.GetHeader("refer-sub")
		if header == nil {
			t.Fatal("got 'nil' refer-sub header")
		}
		value = header.Value()
		if value != "true" {
			t.Fatalf("got refer-sub header value %v, want true", value)
		}
		header = req.GetHeader("supported")
		if header == nil {
			t.Fatal("got 'nil' supported header")
		}
		value = header.Value()
		if value != "replaces" {
			t.Fatalf("got supported header value %v, want replaces", value)
		}
	})
	t.Run("PUBLISH", func(t *testing.T) {
		req := sip.Request{}
		ok := AddMethodHeaders(&req, sip.PUBLISH)
		if !ok {
			t.Fatal("got false ok")
		}
		headers := req.Headers()
		if len(headers) != 2 {
			t.Fatalf("got %d headers, want 2", len(headers))
		}
		header := req.GetHeader("event")
		if header == nil {
			t.Fatal("got 'nil' event header")
		}
		value := header.Value()
		if value != expectedPresenceHeaderValue {
			t.Fatalf("got event header value %v, want presence", value)
		}
		header = req.GetHeader("expires")
		if header == nil {
			t.Fatal("got 'nil' expires header")
		}
		value = header.Value()
		if value != "3600" {
			t.Fatalf("got expires header value %v, want 3600", value)
		}
	})
	t.Run("MESSAGE", func(t *testing.T) {
		req := sip.Request{}
		ok := AddMethodHeaders(&req, sip.MESSAGE)
		if !ok {
			t.Fatal("got false ok")
		}
		headers := req.Headers()
		if len(headers) != 0 {
			t.Fatalf("got %d headers, want 0", len(headers))
		}
	})
}

func TestNewRequestBody(t *testing.T) {
	tests := []struct {
		name                string
		method              sip.RequestMethod
		expectedBodyInclude string
		expectedHeader      string
	}{
		{
			"INVITE",
			sip.INVITE,
			"o=caller",
			"application/sdp",
		},
		{
			"MESSAGE",
			sip.MESSAGE,
			"Hello, this is a test message",
			"text/plain",
		},
		{
			"INFO",
			sip.INFO,
			"Signal=1Signal=1\nDuration=10",
			"application/dtmf-relay",
		},
		{"ACK", sip.ACK, "", ""},
		{"CANCEL", sip.CANCEL, "", ""},
		{"BYE", sip.BYE, "", ""},
		{"REGISTER", sip.REGISTER, "", ""},
		{"OPTIONS", sip.OPTIONS, "", ""},
		{"SUBSCRIBE", sip.SUBSCRIBE, "", ""},
		{
			"NOTIFY",
			sip.NOTIFY,
			"",
			"application/pidf+xml",
		},
		{"REFER", sip.REFER, "", ""},
		{
			"PUBLISH",
			sip.PUBLISH,
			`presence xmlns="urn:ietf:params:xml:ns:pidf`,
			"application/pidf+xml",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			body, header := NewRequestBody(tt.method)
			if !strings.Contains(body, tt.expectedBodyInclude) {
				t.Fatalf("got body %v, want %v", body, tt.expectedBodyInclude)
			}
			if header != tt.expectedHeader {
				t.Fatalf("got header %v, want %v", header, tt.expectedHeader)
			}
		})
	}
}

func TestNewDefaultInviteBody(t *testing.T) {
	t.Run("returns a valid body", func(t *testing.T) {
		body := NewDefaultInviteBody("", "", "")
		if body == "" {
			t.Fatal("got empty")
		}
		if !strings.Contains(body, "o=caller") {
			t.Fatalf("got %s, want o=caller", body)
		}
		if !strings.Contains(body, "s=-") {
			t.Fatalf("got %s, want s=-", body)
		}
		if !strings.Contains(body, "c=IN IP4") {
			t.Fatalf("got %s, want c=IN IP4", body)
		}
		expected := `t=0 0
m=audio 5004 RTP/AVP 0
a=rtpmap:0 PCMU/8000`
		if !strings.Contains(body, expected) {
			t.Fatalf("got %s, want %s", body, expected)
		}
	})
	t.Run("returns a valid body with custom parameters", func(t *testing.T) {
		body := NewDefaultInviteBody("host-0", "sess-0", "sessver-0")
		expected := `v=0
o=caller sess-0 sessver-0 IN IP4 host-0
s=-
c=IN IP4 host-0
t=0 0
m=audio 5004 RTP/AVP 0
a=rtpmap:0 PCMU/8000`
		if body != expected {
			t.Fatalf("got %s, want %s", body, expected)
		}
	})
}

func TestNewDefaultPublishBody(t *testing.T) {
	t.Run("returns a valid body", func(t *testing.T) {
		body := NewDefaultPublishBody("", "", "")
		if body == "" {
			t.Fatal("got empty")
		}
		expected := `<?xml version="1.0" encoding="UTF-8"?>
<presence xmlns="urn:ietf:params:xml:ns:pidf"`
		if !strings.Contains(body, expected) {
			t.Fatalf("got %s, want %s", body, expected)
		}
		if !strings.Contains(body, "<tuple id=") {
			t.Fatalf("got %s, want <tuple id=", body)
		}
		expected = `<status>
			<basic>open</basic>
		</status>
	</tuple>
</presence>`
		if !strings.Contains(body, expected) {
			t.Fatalf("got %s, want %s", body, expected)
		}
	})
	t.Run("returns a valid body with custom parameters", func(t *testing.T) {
		body := NewDefaultPublishBody("id-0", "status-0", "entity-0")
		expected := `<?xml version="1.0" encoding="UTF-8"?>
<presence xmlns="urn:ietf:params:xml:ns:pidf"
					entity="entity-0">
	<tuple id="id-0">
		<status>
			<basic>status-0</basic>
		</status>
	</tuple>
</presence>`
		if body != expected {
			t.Fatalf("got %s, want %s", body, expected)
		}
	})
}

func TestNewDefaultNotifyBody(t *testing.T) {
	t.Run("returns a valid body", func(t *testing.T) {
		body := NewDefaultNotifyBody("", "", "", "")
		if body == "" {
			t.Fatal("got empty")
		}
		expected := `<?xml version="1.0"?>
<presence xmlns="urn:ietf:params:xml:ns:pidf">
	<tuple id="`
		if !strings.Contains(body, expected) {
			t.Fatalf("got %s, want %s", body, expected)
		}
		expected = `<status>
			<basic>open</basic>
		</status>
		<contact>sip:bob@`
		if !strings.Contains(body, expected) {
			t.Fatalf("got %s, want %s", body, expected)
		}
		expected = `</contact>
		<timestamp>`
		if !strings.Contains(body, expected) {
			t.Fatalf("got %s, want %s", body, expected)
		}
		expected = `</timestamp>
	</tuple>
</presence>`
		if !strings.Contains(body, expected) {
			t.Fatalf("got %s, want %s", body, expected)
		}
	})
}

func TestParseTransport(t *testing.T) {
	tests := []struct {
		input    string
		expected TransportType
	}{
		{"UDP", UDP},
		{"udp", UDP},
		{"TCP", TCP},
		{"TLS", TLS},
		{"invalid", UNKNOWN},
		{"", UNKNOWN},
	}
	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			result := ParseTransport(tt.input)
			if result != tt.expected {
				t.Fatalf("got %s, want %s", result, tt.expected)
			}
		})
	}
}

func TestParseMethod(t *testing.T) {
	tests := []struct {
		input    string
		expected sip.RequestMethod
	}{
		{"INVITE", sip.INVITE},
		{"invite", sip.INVITE},
		{"ACK", sip.ACK},
		{"BYE", sip.BYE},
		{"CANCEL", sip.CANCEL},
		{"REGISTER", sip.REGISTER},
		{"OPTIONS", sip.OPTIONS},
		{"SUBSCRIBE", sip.SUBSCRIBE},
		{"NOTIFY", sip.NOTIFY},
		{"REFER", sip.REFER},
		{"PUBLISH", sip.PUBLISH},
		{"MESSAGE", sip.MESSAGE},
		{"INFO", sip.INFO},
		{"PRACK", sip.PRACK},
		{"UPDATE", sip.UPDATE},
		{"invalid", ""},
		{"", ""},
	}
	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			result := ParseMethod(tt.input)
			if result != tt.expected {
				t.Fatalf("got %s, want %s", result, tt.expected)
			}
		})
	}
}
