package urlutil

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/pomerium/pomerium/pkg/grpc/identity"
	"github.com/pomerium/pomerium/pkg/hpke"
)

func TestCallbackURL(t *testing.T) {
	t.Parallel()
	k1 := hpke.DerivePrivateKey([]byte("sender"))
	k2 := hpke.DerivePrivateKey([]byte("receiver"))

	rawSignInURL, err := CallbackURL(k1, k2.PublicKey(), url.Values{
		QueryRedirectURI: {"https://redirect.example.com"},
	}, &identity.Profile{
		ProviderId: "IDP-1",
	}, hpke.EncryptURLValuesV1)
	require.NoError(t, err)

	signInURL, err := ParseAndValidateURL(rawSignInURL)
	require.NoError(t, err)

	k3, q, err := hpke.DecryptURLValues(k2, signInURL.Query())
	require.NoError(t, err)
	assert.Equal(t, k1.PublicKey(), k3)
	assert.NotEmpty(t, q.Get(QueryExpiry))
	assert.NotEmpty(t, q.Get(QueryIssued))
	assert.NotEmpty(t, q.Get(QueryVersion))
	assert.Equal(t, "https://redirect.example.com", q.Get(QueryRedirectURI))
	assert.JSONEq(t, `{ "providerId": "IDP-1" }`, q.Get(QueryIdentityProfile))
}

func TestRedirectURI(t *testing.T) {
	t.Run("query", func(t *testing.T) {
		r, err := http.NewRequest(http.MethodGet, "https://www.example.com?"+(url.Values{
			QueryRedirectURI: {"https://www.example.com/redirect"},
		}).Encode(), nil)
		require.NoError(t, err)

		redirectURI, ok := RedirectURL(r)
		assert.True(t, ok)
		assert.Equal(t, "https://www.example.com/redirect", redirectURI)
	})
	t.Run("form", func(t *testing.T) {
		r, err := http.NewRequest(http.MethodPost, "https://www.example.com", strings.NewReader((url.Values{
			QueryRedirectURI: {"https://www.example.com/redirect"},
		}).Encode()))
		require.NoError(t, err)
		r.Header.Set("Content-Type", "application/x-www-form-urlencoded")

		redirectURI, ok := RedirectURL(r)
		assert.True(t, ok)
		assert.Equal(t, "https://www.example.com/redirect", redirectURI)
	})
	t.Run("cookie", func(t *testing.T) {
		r, err := http.NewRequest(http.MethodGet, "https://www.example.com", nil)
		require.NoError(t, err)
		r.AddCookie(&http.Cookie{
			Name:  QueryRedirectURI,
			Value: "https://www.example.com/redirect",
		})

		redirectURI, ok := RedirectURL(r)
		assert.True(t, ok)
		assert.Equal(t, "https://www.example.com/redirect", redirectURI)
	})
}

func TestSignInURL(t *testing.T) {
	t.Parallel()
	k1 := hpke.DerivePrivateKey([]byte("sender"))
	k2 := hpke.DerivePrivateKey([]byte("receiver"))

	authenticateURL := MustParseAndValidateURL("https://authenticate.example.com")
	redirectURL := MustParseAndValidateURL("https://redirect.example.com")

	rawSignInURL, err := SignInURL(k1, k2.PublicKey(), &authenticateURL, &redirectURL, "IDP-1")
	require.NoError(t, err)

	signInURL, err := ParseAndValidateURL(rawSignInURL)
	require.NoError(t, err)

	k3, q, err := hpke.DecryptURLValues(k2, signInURL.Query())
	require.NoError(t, err)
	assert.Equal(t, k1.PublicKey(), k3)
	assert.NotEmpty(t, q.Get(QueryExpiry))
	assert.NotEmpty(t, q.Get(QueryIssued))
	assert.NotEmpty(t, q.Get(QueryVersion))
	assert.Equal(t, "https://redirect.example.com", q.Get(QueryRedirectURI))
	assert.Equal(t, "IDP-1", q.Get(QueryIdentityProviderID))
}

func TestSignOutURL(t *testing.T) {
	t.Parallel()

	r := httptest.NewRequest(http.MethodGet, "https://route.example.com?"+(url.Values{
		QueryRedirectURI: {"https://www.example.com/redirect"},
	}).Encode(), nil)
	authenticateURL := MustParseAndValidateURL("https://authenticate.example.com")

	rawSignOutURL := SignOutURL(r, &authenticateURL, []byte("TEST"))
	signOutURL, err := ParseAndValidateURL(rawSignOutURL)
	require.NoError(t, err)

	q := signOutURL.Query()
	assert.NotEmpty(t, q.Get(QueryExpiry))
	assert.NotEmpty(t, q.Get(QueryIssued))
	assert.NotEmpty(t, q.Get(QueryVersion))
	assert.NotEmpty(t, q.Get(QueryHmacSignature))
	assert.Equal(t, "https://www.example.com/redirect", q.Get(QueryRedirectURI))
}