package remote

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/hashicorp/go-cleanhttp"
)

func TestHTTPClient_impl(t *testing.T) {
	var _ Client = new(HTTPClient)
	var _ ClientLocker = new(HTTPClient)
}

func TestHTTPClient(t *testing.T) {
	handler := new(testHTTPHandler)
	ts := httptest.NewServer(http.HandlerFunc(handler.Handle))
	defer ts.Close()

	url, err := url.Parse(ts.URL)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test basic get/update
	client := &HTTPClient{URL: url, Client: cleanhttp.DefaultClient()}
	testClient(t, client)

	// Test locking and alternative UpdateMethod
	a := &HTTPClient{
		URL:          url,
		UpdateMethod: "PUT",
		LockURL:      url,
		LockMethod:   "LOCK",
		UnlockURL:    url,
		UnlockMethod: "UNLOCK",
		Client:       cleanhttp.DefaultClient(),
	}
	b := &HTTPClient{
		URL:          url,
		UpdateMethod: "PUT",
		LockURL:      url,
		LockMethod:   "LOCK",
		UnlockURL:    url,
		UnlockMethod: "UNLOCK",
		Client:       cleanhttp.DefaultClient(),
	}
	TestRemoteLocks(t, a, b)

}

func assertError(t *testing.T, err error, expected string) {
	if err == nil {
		t.Fatalf("Expected empty config to err")
	} else if err.Error() != expected {
		t.Fatalf("Expected err.Error() to be \"%s\", got \"%s\"", expected, err.Error())
	}
}

func TestHTTPClientFactory(t *testing.T) {
	// missing address
	_, err := httpFactory(map[string]string{})
	assertError(t, err, "missing 'address' configuration")

	// defaults
	conf := map[string]string{
		"address": "http://127.0.0.1:8888/foo",
	}
	c, err := httpFactory(conf)
	client, _ := c.(*HTTPClient)
	if client == nil || err != nil {
		t.Fatal("Unexpected failure, address")
	}
	if client.URL.String() != conf["address"] {
		t.Fatalf("Expected address \"%s\", got \"%s\"", conf["address"], client.URL.String())
	}
	if client.UpdateMethod != "POST" {
		t.Fatalf("Expected update_method \"%s\", got \"%s\"", "POST", client.UpdateMethod)
	}
	if client.LockURL != nil || client.LockMethod != "LOCK" {
		t.Fatal("Unexpected lock_address or lock_method")
	}
	if client.UnlockURL != nil || client.UnlockMethod != "UNLOCK" {
		t.Fatal("Unexpected unlock_address or unlock_method")
	}
	if client.Username != "" || client.Password != "" {
		t.Fatal("Unexpected username or password")
	}

	// custom
	conf = map[string]string{
		"address":        "http://127.0.0.1:8888/foo",
		"update_method":  "BLAH",
		"lock_address":   "http://127.0.0.1:8888/bar",
		"lock_method":    "BLIP",
		"unlock_address": "http://127.0.0.1:8888/baz",
		"unlock_method":  "BLOOP",
		"username":       "user",
		"password":       "pass",
	}
	c, err = httpFactory(conf)
	client, _ = c.(*HTTPClient)
	if client == nil || err != nil {
		t.Fatal("Unexpected failure, update_method")
	}
	if client.UpdateMethod != "BLAH" {
		t.Fatalf("Expected update_method \"%s\", got \"%s\"", "BLAH", client.UpdateMethod)
	}
	if client.LockURL.String() != conf["lock_address"] || client.LockMethod != "BLIP" {
		t.Fatalf("Unexpected lock_address \"%s\" vs \"%s\" or lock_method \"%s\" vs \"%s\"", client.LockURL.String(),
			conf["lock_address"], client.LockMethod, conf["lock_method"])
	}
	if client.UnlockURL.String() != conf["unlock_address"] || client.UnlockMethod != "BLOOP" {
		t.Fatalf("Unexpected unlock_address \"%s\" vs \"%s\" or unlock_method \"%s\" vs \"%s\"", client.UnlockURL.String(),
			conf["unlock_address"], client.UnlockMethod, conf["unlock_method"])
	}
	if client.Username != "user" || client.Password != "pass" {
		t.Fatalf("Unexpected username \"%s\" vs \"%s\" or password \"%s\" vs \"%s\"", client.Username, conf["username"],
			client.Password, conf["password"])
	}
}

type testHTTPHandler struct {
	Data   []byte
	Locked bool
}

func (h *testHTTPHandler) Handle(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		w.Write(h.Data)
	case "POST", "PUT":
		buf := new(bytes.Buffer)
		if _, err := io.Copy(buf, r.Body); err != nil {
			w.WriteHeader(500)
		}

		h.Data = buf.Bytes()
	case "LOCK":
		if h.Locked {
			w.WriteHeader(423)
		} else {
			h.Locked = true
		}
	case "UNLOCK":
		h.Locked = false
	case "DELETE":
		h.Data = nil
		w.WriteHeader(200)
	default:
		w.WriteHeader(500)
		w.Write([]byte(fmt.Sprintf("Unknown method: %s", r.Method)))
	}
}
