package auth

import (
	"html/template"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
	"time"

	"code.secondbit.org/uuid"
)

const (
	scopeSet = 1 << iota
	stateSet
	uriSet
)

func TestGetGrantCodeSuccess(t *testing.T) {
	t.Parallel()
	store := NewMemstore()
	testContext := Context{
		template: template.Must(template.New(getGrantTemplateName).Parse("Get auth grant")),
		clients:  store,
		grants:   store,
		profiles: store,
		tokens:   store,
	}
	client := Client{
		ID:      uuid.NewID(),
		Secret:  "super secret!",
		OwnerID: uuid.NewID(),
		Name:    "My test client",
		Logo:    "https://secondbit.org/logo.png",
		Website: "https://secondbit.org",
		Type:    "public",
	}
	uri, err := url.Parse("https://test.secondbit.org/redirect")
	if err != nil {
		t.Fatal("Can't parse URL:", err)
	}
	endpoint := Endpoint{
		ID:       uuid.NewID(),
		ClientID: client.ID,
		URI:      *uri,
		Added:    time.Now(),
	}
	err = testContext.SaveClient(client)
	if err != nil {
		t.Fatal("Can't store client:", err)
	}
	err = testContext.AddEndpoint(client.ID, endpoint)
	if err != nil {
		t.Fatal("Can't store endpoint:", err)
	}
	req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
	if err != nil {
		t.Fatal("Can't build request:", err)
	}
	for i := 0; i < 1<<3; i++ {
		w := httptest.NewRecorder()
		params := url.Values{}
		// see OAuth 2.0 spec, section 4.1.1
		params.Set("response_type", "code")
		params.Set("client_id", client.ID.String())
		if i&uriSet != 0 {
			params.Set("redirect_uri", endpoint.URI.String())
		}
		if i&scopeSet != 0 {
			params.Set("scope", "testscope")
		}
		if i&stateSet != 0 {
			params.Set("state", "my super secure state string")
		}
		req.URL.RawQuery = params.Encode()
		GetGrantHandler(w, req, testContext)
		if w.Code != http.StatusOK {
			t.Errorf("Expected status code to be %d, got %d for %s", http.StatusOK, w.Code, req.URL.String())
		}
		if w.Body.String() != "Get auth grant" {
			t.Errorf("Expected body to be `%s`, got `%s` for %s", "Get auth grant", w.Body.String(), req.URL.String())
		}
	}
}

func TestGetGrantCodeInvalidURI(t *testing.T) {
	t.Parallel()
	store := NewMemstore()
	testContext := Context{
		template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
		clients:  store,
		grants:   store,
		profiles: store,
		tokens:   store,
	}
	client := Client{
		ID:      uuid.NewID(),
		Secret:  "super secret!",
		OwnerID: uuid.NewID(),
		Name:    "My test client",
		Type:    "public",
	}
	uri, err := url.Parse("https://test.secondbit.org/redirect")
	if err != nil {
		t.Fatal("Can't parse URL:", err)
	}
	endpoint := Endpoint{
		ID:       uuid.NewID(),
		ClientID: client.ID,
		URI:      *uri,
		Added:    time.Now(),
	}
	err = testContext.SaveClient(client)
	if err != nil {
		t.Fatal("Can't store client:", err)
	}
	err = testContext.AddEndpoint(client.ID, endpoint)
	if err != nil {
		t.Fatal("Can't store endpoint:", err)
	}
	req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
	if err != nil {
		t.Fatal("Can't build request:", err)
	}
	w := httptest.NewRecorder()
	params := url.Values{}
	params.Set("response_type", "code")
	params.Set("client_id", client.ID.String())
	params.Set("redirect_uri", "https://test.secondbit.org/wrong")
	req.URL.RawQuery = params.Encode()
	GetGrantHandler(w, req, testContext)
	if w.Code != http.StatusBadRequest {
		t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
	}
	if w.Body.String() != "The redirect_uri specified is not valid." {
		t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
	}
}
