package server

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

	"github.com/google/uuid"
)

var (
	testNamespaceName = "col-name-" + uuid.New().String()[:10]
	testNamespaceID   = "col-id-" + uuid.New().String()[:10]
	testEntityName    = "ent-name-" + uuid.New().String()[:10]
	testEntityID      = "ent-id-" + uuid.New().String()[:10]
	testFilename      = "filename-" + uuid.New().String()[:10]
	testContent       = "content-" + uuid.New().String()[:10]
)

func TestRESTRouter(t *testing.T) {
	rest, clean := testREST(t)
	defer clean()
	cases := map[string]struct {
		method string
		is404  bool
	}{
		"/version": {
			method: http.MethodGet,
		},
		fmt.Sprintf(`%s`, config.New().FilePrefix): {
			method: http.MethodGet,
			is404:  true,
		},
		fmt.Sprintf(`%s/`, config.New().FilePrefix): {
			method: http.MethodGet,
			is404:  true,
		},
		fmt.Sprintf(`%s/%s`, config.New().FilePrefix, testFilename): {
			method: http.MethodGet,
		},
		fmt.Sprintf(`%s/fake.fake`, config.New().FilePrefix): {
			method: http.MethodGet,
			is404:  true,
		},
		fmt.Sprintf("/users/%s", testNamespaceID): {
			method: http.MethodGet,
			is404:  true,
		},
		fmt.Sprintf("/users/%s", testNamespaceID): {
			method: http.MethodPost,
			is404:  true,
		},
		"/users/": {
			method: http.MethodGet,
			is404:  true,
		},
		"/users": {
			method: http.MethodPost,
		},
		"/users?": {
			method: http.MethodGet,
		},
		"/entities": {
			method: http.MethodGet,
		},
		"/entities/": {
			method: http.MethodGet,
		},
		fmt.Sprintf("/entities/fake-%s", testEntityID): {
			method: http.MethodGet,
			is404:  true,
		},
		fmt.Sprintf("/entities/%s", testEntityID): {
			method: http.MethodGet,
		},
		fmt.Sprintf("/entities/%s", testEntityID): {
			method: http.MethodPatch,
		},
		fmt.Sprintf("/entities/%s", testEntityID): {
			method: http.MethodPost,
		},
		fmt.Sprintf("/entities/%s", testEntityID): {
			method: http.MethodPut,
		},
		fmt.Sprintf("/entities/%s/connections/uuid", testEntityID): {
			method: http.MethodPatch,
		},
		fmt.Sprintf("/entities/%s/connections/uuid", testEntityID): {
			method: http.MethodPut,
		},
		fmt.Sprintf("/entities/%s/connections/uuid", testEntityID): {
			method: http.MethodPost,
			is404:  true,
		},
		fmt.Sprintf("/entities/%s/connections/", testEntityID): {
			method: http.MethodPost,
			is404:  true,
		},
		fmt.Sprintf("/entities/%s/connections/", testEntityID): {
			method: http.MethodPut,
			is404:  true,
		},
		fmt.Sprintf("/entities/%s/connections/", testEntityID): {
			method: http.MethodPatch,
			is404:  true,
		},
		fmt.Sprintf("/entities/%s/connections", testEntityID): {
			method: http.MethodPost,
			is404:  true,
		},
		fmt.Sprintf("/entities/%s/connections", testEntityID): {
			method: http.MethodPut,
		},
		fmt.Sprintf("/entities/%s/connections", testEntityID): {
			method: http.MethodPatch,
		},
		fmt.Sprintf("/entities/%s/connections/uuid", testEntityID): {
			method: http.MethodDelete,
		},
		fmt.Sprintf("/entities/%s/connections/", testEntityID): {
			method: http.MethodDelete,
			is404:  true,
		},
		fmt.Sprintf("/entities/%s/connections", testEntityID): {
			method: http.MethodDelete,
		},
		fmt.Sprintf("/entities/%s/", testEntityID): {
			method: http.MethodDelete,
			is404:  true,
		},
		fmt.Sprintf("/entities/%s", testEntityID): {
			method: http.MethodDelete,
		},
	}

	for name, d := range cases {
		c := d
		path := name
		t.Run(name, func(t *testing.T) {
			r := httptest.NewRequest(c.method, path, strings.NewReader(``))
			w := httptest.NewRecorder()
			rest.router.ServeHTTP(w, r)
			if (w.Code == http.StatusNotFound) != c.is404 {
				t.Fatalf("want 404==%v, got %d: %+v: %s", c.is404, w.Code, c, w.Body.Bytes())
			}
		})
	}
}

func testREST(t *testing.T) (*REST, func()) {
	d, err := ioutil.TempDir(os.TempDir(), "tempdir.*")
	if err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(path.Join(d, testFilename), []byte(testContent), os.ModePerm); err != nil {
		t.Fatal(err)
	}
	os.Setenv("FILEROOT", d)
	os.Setenv("DRIVER_TYPE", "map")
	os.Setenv("AUTH", "false")
	os.Args = os.Args[:1]
	os.Args = os.Args[:1]
	rest, err := NewREST(storage.NewRateLimitedGraph(""))
	if err != nil {
		t.Fatal(err)
	}
	ctx, can := context.WithTimeout(context.Background(), time.Second*5)
	defer can()
	one := randomOne()
	one.Name = testEntityName
	one.ID = testEntityID
	if err := rest.g.Insert(ctx, testNamespaceID, one); err != nil {
		t.Fatal(err)
	}
	if err := rest.g.Insert(ctx, testNamespaceID+"."+AuthKey, entity.One{
		Name:  testNamespaceName,
		ID:    testNamespaceID,
		Title: "title",
	}); err != nil {
		t.Fatal(err)
	}
	if err := rest.g.Insert(ctx, testNamespaceID+"."+AuthKey, entity.One{
		Name:  UserKey,
		ID:    UserKey,
		Title: "",
	}); err != nil {
		t.Fatal(err)
	}
	return rest, func() {
		os.RemoveAll(d)
	}
}

func randomOne() entity.One {
	return entity.One{
		ID:          "iddd-" + uuid.New().String()[:5],
		Name:        "name-" + uuid.New().String()[:5],
		Type:        "type-" + uuid.New().String()[:5],
		Title:       "titl-" + uuid.New().String()[:5],
		Text:        "text-" + uuid.New().String()[:5],
		Modified:    time.Now().UnixNano(),
		Connections: map[string]entity.One{},
		Attachments: map[string]string{
			uuid.New().String()[:5]: uuid.New().String()[:5],
		},
	}
}
