package wrouter

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

	"git.aiterp.net/gisle/wrouter/generate"
	"git.aiterp.net/gisle/wrouter/response"

	"git.aiterp.net/gisle/wrouter/auth"
)

var pages = []Page{
	Page{generate.ID(), "Test Page", "Blurg blurg"},
	Page{generate.ID(), "Test Page 2", "Blurg blurg 2"},
	Page{generate.ID(), "Stuff", "And things"},
}

type Page struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	Text  string `json:"text"`
}

type Header struct {
	ID    string `json:"id"`
	Title string `json:"title"`
}

type PageForm struct {
	Title string `json:"title"`
	Text  string `json:"text"`
}

func listPage(w http.ResponseWriter, req *http.Request, user *auth.User) {
	headers := make([]Header, len(pages))
	for i, page := range pages {
		headers[i] = Header{page.ID, page.Title}
	}

	response.JSON(w, 200, headers)
}

func createPage(w http.ResponseWriter, req *http.Request, user *auth.User) {
	title := req.Form.Get("title")
	text := req.Form.Get("text")

	if title == "" {
		response.Text(w, 400, "No title")
		return
	}

	for _, page := range pages {
		if page.Title == title {
			response.Text(w, 400, "Title already exists")
			return
		}
	}

	page := Page{generate.ID(), title, text}
	pages = append(pages, page)

	response.JSON(w, 200, page)
}

func getPage(w http.ResponseWriter, req *http.Request, id string, user *auth.User) {
	for _, page := range pages {
		if page.ID == id {
			response.JSON(w, 200, page)
			return
		}
	}

	response.Text(w, 404, "Page not found")
}

func updatePage(w http.ResponseWriter, req *http.Request, id string, user *auth.User) {
	for _, page := range pages {
		if page.ID == id {
			title := req.Form.Get("title")
			text := req.Form.Get("text")

			if title != "" {
				page.Title = title
			}
			page.Text = text

			response.JSON(w, 200, page)
			return
		}
	}

	response.Text(w, 404, "Page not found")
}

func deletePage(w http.ResponseWriter, req *http.Request, id string, user *auth.User) {
	for i, page := range pages {
		if page.ID == id {
			pages = append(pages[:i], pages[i+1:]...)
			response.Empty(w)
			return
		}
	}

	response.Text(w, 404, "Page not found")
}

var resource = Resource{listPage, createPage, getPage, updatePage, deletePage}

type handlerStruct struct{}

func (hs *handlerStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	req.ParseForm() // Router does this in non-tests

	if strings.HasPrefix(req.URL.Path, "/page") {
		resource.Handle("/page", w, req, nil)
		return
	}
}

func runForm(method, url string, data url.Values) (*http.Response, error) {
	body := strings.NewReader(data.Encode())
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	return http.DefaultClient.Do(req)
}

func TestResource(t *testing.T) {
	server := httptest.NewServer(&handlerStruct{})

	t.Run("List", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/page/")
		if err != nil {
			t.Error(err)
			t.Fail()
		}

		if resp.StatusCode != 200 {
			t.Error("Expected status 200, got", resp.StatusCode, resp.Status)
			t.Fail()
		}

		headers := []Header{}
		err = json.NewDecoder(resp.Body).Decode(&headers)
		if err != nil {
			t.Error(err)
			t.Fail()
		}

		if len(headers) < 3 {
			t.Error("Expected 3 headers, got", len(headers))
			t.Fail()
		}

		for i, header := range headers {
			page := pages[i]

			if header.ID != page.ID {
				t.Error(header.ID, "!=", page.ID)
				t.Fail()
			}

			if header.Title != page.Title {
				t.Error(header.Title, "!=", page.Title)
				t.Fail()
			}
		}
	})

	t.Run("Get", func(t *testing.T) {
		page := pages[1]
		resp, err := http.Get(server.URL + "/page/" + page.ID)
		if err != nil {
			t.Error(err)
			t.Fail()
		}

		if resp.StatusCode != 200 {
			t.Error("Expected status 200, got", resp.StatusCode, resp.Status)
			t.Fail()
		}

		respPage := Page{}
		err = json.NewDecoder(resp.Body).Decode(&respPage)
		if err != nil {
			t.Error(err)
			t.Fail()
		}

		if respPage.ID == "" {
			t.Error("No ID in response page")
			t.Fail()
		}

		if respPage.ID != page.ID {
			t.Errorf("ID %s != %s", respPage.ID, page.ID)
			t.Fail()
		}

		if respPage.Title != page.Title {
			t.Errorf("Title %s != %s", respPage.Title, page.Title)
			t.Fail()
		}

		if respPage.Text != page.Text {
			t.Errorf("Text %s != %s", respPage.Text, page.Text)
			t.Fail()
		}
	})

	t.Run("Get_Fail", func(t *testing.T) {
		resp, err := http.Get(server.URL + "/page/" + generate.ID())
		if err != nil {
			t.Error(err)
			t.Fail()
		}

		if resp.StatusCode != 404 {
			t.Error("Expected status 404, got", resp.StatusCode, resp.Status)
			t.Fail()
		}

		respPage := Page{}
		err = json.NewDecoder(resp.Body).Decode(&respPage)
		if err == nil {
			t.Error("Expected encoder error, got:", respPage)
			t.Fail()
		}

		if respPage.ID != "" {
			t.Error("Ad ID in response page", respPage.ID)
			t.Fail()
		}
	})

	t.Run("Create", func(t *testing.T) {
		form := url.Values{}
		form.Set("title", "Hello World")
		form.Set("text", "Sei Gegrüßt, Erde")

		resp, err := http.PostForm(server.URL+"/page/", form)

		if err != nil {
			t.Error(err)
			t.Fail()
		}

		if resp.StatusCode != 200 {
			t.Error("Expected status 200, got", resp.StatusCode, resp.Status)
			t.Fail()
		}

		respPage := Page{}
		err = json.NewDecoder(resp.Body).Decode(&respPage)
		if err != nil {
			t.Error(err)
			t.Fail()
		}

		if respPage.ID == "" {
			t.Error("No ID in response page")
			t.Fail()
		}

		if respPage.Title != form.Get("title") {
			t.Errorf("Title %s != %s", respPage.Title, form.Get("title"))
			t.Fail()
		}

		if respPage.Text != form.Get("text") {
			t.Errorf("Text %s != %s", respPage.Text, form.Get("text"))
			t.Fail()
		}

		if len(pages) != 4 {
			t.Errorf("Page was not added")
			t.Fail()
		}
	})

	t.Run("Update", func(t *testing.T) {
		page := pages[0]
		form := url.Values{}
		form.Set("text", "Edits and stuff")

		resp, err := runForm("PUT", server.URL+"/page/"+page.ID, form)

		if err != nil {
			t.Error("Request:", err)
			t.Fail()
		}

		if resp.StatusCode != 200 {
			t.Error("Expected status 200, got", resp.StatusCode, resp.Status)
			t.Fail()
		}

		respPage := Page{}
		err = json.NewDecoder(resp.Body).Decode(&respPage)
		if err != nil {
			t.Error("Decode:", err)
			t.Fail()
		}

		if respPage.ID == "" {
			t.Error("No ID in response page")
			t.Fail()
		}

		if respPage.Title != page.Title {
			t.Errorf("Title %s != %s", respPage.Title, form.Get("title"))
			t.Fail()
		}

		if respPage.Text != form.Get("text") {
			t.Errorf("Text %s != %s", respPage.Text, form.Get("text"))
			t.Fail()
		}
	})

	t.Run("Update_Fail", func(t *testing.T) {
		form := url.Values{}
		form.Set("text", "Edits and stuff")

		resp, err := runForm("PUT", server.URL+"/page/NONEXISTENT-ID-GOES-HERE", form)

		if err != nil {
			t.Error("Request:", err)
			t.Fail()
		}

		if resp.StatusCode != 404 {
			t.Error("Expected status 404, got", resp.StatusCode, resp.Status)
			t.Fail()
		}

		respPage := Page{}
		err = json.NewDecoder(resp.Body).Decode(&respPage)
		if err == nil {
			t.Error("A page was returned:", respPage)
			t.Fail()
		}

		if respPage.ID != "" {
			t.Error("ID in response page", respPage.ID)
			t.Fail()
		}
	})

	t.Run("Delete", func(t *testing.T) {
		page := pages[3]
		resp, err := runForm("DELETE", server.URL+"/page/"+page.ID, url.Values{})

		if err != nil {
			t.Error("Request:", err)
			t.Fail()
		}

		if resp.StatusCode != 204 {
			t.Error("Expected status 204, got", resp.Status)
			t.Fail()
		}

		if len(pages) != 3 {
			t.Error("Page was not deleted")
			t.Fail()
		}
	})

	t.Run("Delete_Fail", func(t *testing.T) {
		resp, err := runForm("DELETE", server.URL+"/page/NONEXISTENT-ID-GOES-HERE", url.Values{})

		if err != nil {
			t.Error("Request:", err)
			t.Fail()
		}

		if resp.StatusCode != 404 {
			t.Error("Expected status 404, got", resp.Status)
			t.Fail()
		}

		if len(pages) != 3 {
			t.Error("A page was deleted despite the non-existent ID")
			t.Fail()
		}
	})
}