package deploy

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestXMLEscape(t *testing.T) {
	got := xmlEscape(`a&b<c>d`)
	want := "a&amp;b&lt;c&gt;d"
	if got != want {
		t.Fatalf("got %q want %q", got, want)
	}
}

func TestIntSliceStr(t *testing.T) {
	got := intSliceStr([]int{22, 445, 5985})
	want := []string{"22", "445", "5985"}
	if len(got) != len(want) {
		t.Fatalf("len %d != %d", len(got), len(want))
	}
	for i := range want {
		if got[i] != want[i] {
			t.Fatalf("[%d] got %q want %q", i, got[i], want[i])
		}
	}
}

func TestGetSubnet(t *testing.T) {
	if got := getSubnet("192.168.1.42"); got != "192.168.1" {
		t.Fatalf("got %q", got)
	}
	if getSubnet("bad") != "" {
		t.Fatal("invalid ip should return empty")
	}
	if getSubnet("10.0.0.1") != "10.0.0" {
		t.Fatalf("got %q", getSubnet("10.0.0.1"))
	}
}

func TestCloseUPnPInvalidPort(t *testing.T) {
	_, err := CloseUPnP(0)
	if err == nil || !strings.Contains(err.Error(), "external port required") {
		t.Fatalf("expected port error, got %v", err)
	}
}

func TestResolveWANControlURL(t *testing.T) {
	const igdXML = `<?xml version="1.0"?>
<root>
  <device>
    <serviceList>
      <service>
        <serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
        <controlURL>/ctl/IPConn</controlURL>
      </service>
    </serviceList>
  </device>
</root>`

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, igdXML)
	}))
	defer srv.Close()

	got, err := resolveWANControlURL(srv.URL + "/igd.xml")
	if err != nil {
		t.Fatal(err)
	}
	want := srv.URL + "/ctl/IPConn"
	if got != want {
		t.Fatalf("got %q want %q", got, want)
	}
}

func TestResolveWANControlURLAbsolute(t *testing.T) {
	const igdXML = `<?xml version="1.0"?>
<root>
  <service>
    <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType>
    <controlURL>http://192.168.0.1:49152/ctl/IPConn</controlURL>
  </service>
</root>`

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, igdXML)
	}))
	defer srv.Close()

	got, err := resolveWANControlURL(srv.URL + "/desc.xml")
	if err != nil {
		t.Fatal(err)
	}
	if got != "http://192.168.0.1:49152/ctl/IPConn" {
		t.Fatalf("got %q", got)
	}
}

func TestResolveWANControlURLMissingService(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `<root><service><controlURL>/x</controlURL></service></root>`)
	}))
	defer srv.Close()

	_, err := resolveWANControlURL(srv.URL)
	if err == nil || !strings.Contains(err.Error(), "WANIPConnection service not found") {
		t.Fatalf("expected service error, got %v", err)
	}
}

func TestResolveWANControlURLMissingControlURL(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `<root><serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType></root>`)
	}))
	defer srv.Close()

	_, err := resolveWANControlURL(srv.URL)
	if err == nil || !strings.Contains(err.Error(), "controlURL not found") {
		t.Fatalf("expected controlURL error, got %v", err)
	}
}

func TestUpnpGetExternalIP(t *testing.T) {
	const soapResp = `<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
      <NewExternalIPAddress>203.0.113.10</NewExternalIPAddress>
    </u:GetExternalIPAddressResponse>
  </s:Body>
</s:Envelope>`

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method", http.StatusMethodNotAllowed)
			return
		}
		fmt.Fprint(w, soapResp)
	}))
	defer srv.Close()

	ip, err := upnpGetExternalIP(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	if ip != "203.0.113.10" {
		t.Fatalf("got %q", ip)
	}
}

func TestUpnpSOAPErrorResponse(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `<errorCode>718</errorCode>`)
	}))
	defer srv.Close()

	_, err := upnpSOAP(srv.URL, "AddPortMapping", "<body/>")
	if err == nil || !strings.Contains(err.Error(), "AddPortMapping failed") {
		t.Fatalf("expected SOAP error, got %v", err)
	}
}

func TestReLocationParsesSSDPResponse(t *testing.T) {
	cases := []struct {
		body string
		want string
	}{
		{
			"HTTP/1.1 200 OK\r\nLOCATION: http://192.168.0.1:49152/desc.xml\r\n\r\n",
			"http://192.168.0.1:49152/desc.xml",
		},
		{
			"location: http://10.0.0.1/igd.xml",
			"http://10.0.0.1/igd.xml",
		},
	}
	for _, tc := range cases {
		m := reLocation.FindStringSubmatch(tc.body)
		if len(m) != 2 {
			t.Fatalf("no LOCATION match in %q", tc.body)
		}
		if got := strings.TrimSpace(m[1]); got != tc.want {
			t.Fatalf("got %q want %q", got, tc.want)
		}
	}
}

func TestResolveWANControlURLRelativeWithoutLeadingSlash(t *testing.T) {
	const igdXML = `<?xml version="1.0"?>
<root>
  <service>
    <serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
    <controlURL>ctl/IPConn</controlURL>
  </service>
</root>`

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, igdXML)
	}))
	defer srv.Close()

	got, err := resolveWANControlURL(srv.URL + "/igd.xml")
	if err != nil {
		t.Fatal(err)
	}
	want := srv.URL + "/ctl/IPConn"
	if got != want {
		t.Fatalf("got %q want %q", got, want)
	}
}

func TestUpnpSOAPRequestHeaders(t *testing.T) {
	var contentType, soapAction string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		contentType = r.Header.Get("Content-Type")
		soapAction = r.Header.Get("SOAPAction")
		fmt.Fprint(w, `<response/>`)
	}))
	defer srv.Close()

	if _, err := upnpSOAP(srv.URL, "GetExternalIPAddress", "<body/>"); err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(contentType, "text/xml") {
		t.Fatalf("Content-Type: %q", contentType)
	}
	wantAction := `"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"`
	if soapAction != wantAction {
		t.Fatalf("SOAPAction: got %q want %q", soapAction, wantAction)
	}
}

func TestUpnpDeletePortMapping(t *testing.T) {
	var gotBody, gotAction string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotAction = r.Header.Get("SOAPAction")
		buf := make([]byte, 4096)
		n, _ := r.Body.Read(buf)
		gotBody = string(buf[:n])
		fmt.Fprint(w, `<?xml version="1.0"?><ok/>`)
	}))
	defer srv.Close()

	if err := upnpDeletePortMapping(srv.URL, 8989); err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(gotAction, "DeletePortMapping") {
		t.Fatalf("SOAPAction: %q", gotAction)
	}
	if !strings.Contains(gotBody, "<NewExternalPort>8989</NewExternalPort>") {
		t.Fatalf("body missing port: %q", gotBody)
	}
	if !strings.Contains(gotBody, "<NewProtocol>TCP</NewProtocol>") {
		t.Fatalf("body missing protocol: %q", gotBody)
	}
}
