package view

import (
	"context"
	"fmt"
	"io/ioutil"
	"local/dndex/storage"
	"local/dndex/storage/entity"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/google/uuid"
)

func TestAuth(t *testing.T) {
	os.Args = os.Args[:1]
	f, err := ioutil.TempFile(os.TempDir(), "pattern*")
	if err != nil {
		t.Fatal(err)
	}
	f.Close()
	defer os.Remove(f.Name())
	os.Setenv("DBURI", f.Name())

	os.Setenv("AUTH", "true")
	defer os.Setenv("AUTH", "false")

	g := storage.NewGraph()
	handler := jsonHandler(g)

	if err := g.Insert(context.TODO(), "col."+AuthKey, entity.One{Name: UserKey, Title: "password"}); err != nil {
		t.Fatal(err)
	}

	t.Run("auth: no namespace", func(t *testing.T) {
		r := httptest.NewRequest(http.MethodGet, "/who", nil)
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusBadRequest {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}
	})

	t.Run("auth: bad provided", func(t *testing.T) {
		r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil)
		r.Header.Set("Cookie", fmt.Sprintf("%s=not-a-real-token", AuthKey))
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusSeeOther {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}
	})

	t.Run("auth: expired provided", func(t *testing.T) {
		os.Setenv("AUTHLIFETIME", "1ms")
		defer os.Setenv("AUTHLIFETIME", "1h")
		one := entity.One{Name: uuid.New().String(), Title: "title"}
		if err := g.Insert(context.TODO(), "col", one); err != nil {
			t.Fatal(err)
		}
		time.Sleep(time.Millisecond * 50)
		r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil)
		r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, one.Name))
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusSeeOther {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}
	})

	t.Run("auth: none provided: who", func(t *testing.T) {
		r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil)
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusSeeOther {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}
	})

	t.Run("auth: none provided: files", func(t *testing.T) {
		r := httptest.NewRequest(http.MethodGet, "/__files__/myfile?namespace=col", nil)
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusSeeOther {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}
	})

	t.Run("auth: provided", func(t *testing.T) {
		os.Setenv("AUTHLIFETIME", "1h")
		one := entity.One{Name: uuid.New().String(), Title: "title"}
		if err := g.Insert(context.TODO(), "col."+AuthKey, one); err != nil {
			t.Fatal(err)
		}
		r := httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil)
		r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, one.Name))
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusOK {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}
	})

	t.Run("auth: request unknown namespace", func(t *testing.T) {
		os.Setenv("AUTHLIFETIME", "1h")
		r := httptest.NewRequest(http.MethodTrace, "/who?namespace=not-col", nil)
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusNotFound {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}
	})

	t.Run("auth: request", func(t *testing.T) {
		os.Setenv("AUTHLIFETIME", "1h")
		r := httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil)
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusSeeOther {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}

		rawtoken := getCookie(NewAuthKey, w.Header())
		if rawtoken == "" {
			t.Fatal(w.Header())
		}
		token, err := aesDec("password", rawtoken)
		if err != nil {
			t.Fatal(err)
		}

		r = httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil)
		w = httptest.NewRecorder()
		r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, token))
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusOK {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}

		r = httptest.NewRequest(http.MethodTrace, "/__files__/myfile?namespace=col", nil)
		w = httptest.NewRecorder()
		r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, token))
		handler.ServeHTTP(w, r)
		if w.Code != http.StatusNotFound {
			t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
		}
	})
}

func TestAES(t *testing.T) {
	for _, plaintext := range []string{"", "payload!", "a really long payload here"} {
		key := "password"

		enc, err := aesEnc(key, plaintext)
		if err != nil {
			t.Fatal("cannot enc:", err)
		}
		if enc == plaintext {
			t.Fatal(enc)
		}

		dec, err := aesDec(key, enc)
		if err != nil {
			t.Fatal("cannot dec:", err)
		}
		if dec != plaintext {
			t.Fatalf("want decrypted %q, got %q", plaintext, dec)
		}
	}
}
