package client

import (
	"bytes"
	"io"
	"net/http"
	"strings"
	"testing"
)

type nopCloser struct {
	io.Reader
}

func (nopCloser) Close() error { return nil }

func TestHandleHTTPResponseError200ValidBody(t *testing.T) {
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
	}
	err := HandleHTTPResponseError(response)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}
}

func TestHandleHTTPResponseError401ValidBody(t *testing.T) {
	json := `{"errors":[{"code":"UNAUTHORIZED","message":"action requires authentication"}]}`
	response := &http.Response{
		Status:     "401 Unauthorized",
		StatusCode: 401,
		Body:       nopCloser{bytes.NewBufferString(json)},
		Header:     http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
	}
	err := HandleHTTPResponseError(response)

	expectedMsg := "unauthorized: action requires authentication"
	if !strings.Contains(err.Error(), expectedMsg) {
		t.Errorf("Expected %q, got: %q", expectedMsg, err.Error())
	}
}

func TestHandleHTTPResponseError401WithInvalidBody(t *testing.T) {
	json := "{invalid json}"
	response := &http.Response{
		Status:     "401 Unauthorized",
		StatusCode: 401,
		Body:       nopCloser{bytes.NewBufferString(json)},
		Header:     http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
	}
	err := HandleHTTPResponseError(response)

	expectedMsg := "unauthorized: authentication required"
	if !strings.Contains(err.Error(), expectedMsg) {
		t.Errorf("Expected %q, got: %q", expectedMsg, err.Error())
	}
}

func TestHandleHTTPResponseError401WithNoBody(t *testing.T) {
	json := ""
	response := &http.Response{
		Status:     "401 Unauthorized",
		StatusCode: 401,
		Body:       nopCloser{bytes.NewBufferString(json)},
		Header:     http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
	}
	err := HandleHTTPResponseError(response)

	expectedMsg := "unauthorized: "
	if !strings.Contains(err.Error(), expectedMsg) {
		t.Errorf("Expected %q, got: %q", expectedMsg, err.Error())
	}
}

func TestHandleHTTPResponseErrorExpectedStatusCode400ValidBody(t *testing.T) {
	json := `{"errors":[{"code":"DIGEST_INVALID","message":"provided digest does not match"}]}`
	response := &http.Response{
		Status:     "400 Bad Request",
		StatusCode: 400,
		Body:       nopCloser{bytes.NewBufferString(json)},
		Header:     http.Header{"Content-Type": []string{"application/json"}},
	}
	err := HandleHTTPResponseError(response)

	expectedMsg := "digest invalid: provided digest does not match"
	if !strings.Contains(err.Error(), expectedMsg) {
		t.Errorf("Expected %q, got: %q", expectedMsg, err.Error())
	}
}

func TestHandleHTTPResponseErrorExpectedStatusCode404EmptyErrorSlice(t *testing.T) {
	json := `{"randomkey": "randomvalue"}`
	response := &http.Response{
		Status:     "404 Not Found",
		StatusCode: 404,
		Body:       nopCloser{bytes.NewBufferString(json)},
		Header:     http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
	}
	err := HandleHTTPResponseError(response)

	expectedMsg := `error parsing HTTP 404 response body: no error details found in HTTP response body: "{\"randomkey\": \"randomvalue\"}"`
	if !strings.Contains(err.Error(), expectedMsg) {
		t.Errorf("Expected %q, got: %q", expectedMsg, err.Error())
	}
}

func TestHandleHTTPResponseErrorExpectedStatusCode404InvalidBody(t *testing.T) {
	json := "{invalid json}"
	response := &http.Response{
		Status:     "404 Not Found",
		StatusCode: 404,
		Body:       nopCloser{bytes.NewBufferString(json)},
		Header:     http.Header{"Content-Type": []string{"application/json"}},
	}
	err := HandleHTTPResponseError(response)

	expectedMsg := "error parsing HTTP 404 response body: invalid character 'i' looking for beginning of object key string: \"{invalid json}\""
	if !strings.Contains(err.Error(), expectedMsg) {
		t.Errorf("Expected %q, got: %q", expectedMsg, err.Error())
	}
}

func TestHandleHTTPResponseErrorUnexpectedStatusCode501(t *testing.T) {
	response := &http.Response{
		Status:     "501 Not Implemented",
		StatusCode: 501,
		Body:       nopCloser{bytes.NewBufferString("{\"Error Encountered\" : \"Function not implemented.\"}")},
		Header:     http.Header{"Content-Type": []string{"application/json"}},
	}
	err := HandleHTTPResponseError(response)

	expectedMsg := "received unexpected HTTP status: 501 Not Implemented"
	if !strings.Contains(err.Error(), expectedMsg) {
		t.Errorf("Expected %q, got: %q", expectedMsg, err.Error())
	}
}

func TestHandleHTTPResponseErrorInsufficientPrivileges403(t *testing.T) {
	json := `{"details":"requesting higher privileges than access token allows"}`
	response := &http.Response{
		Status:     "403 Forbidden",
		StatusCode: 403,
		Body:       nopCloser{bytes.NewBufferString(json)},
		Header:     http.Header{"Content-Type": []string{"application/json"}},
	}
	err := HandleHTTPResponseError(response)

	expectedMsg := "denied: requesting higher privileges than access token allows"
	if !strings.Contains(err.Error(), expectedMsg) {
		t.Errorf("Expected %q, got: %q", expectedMsg, err.Error())
	}
}

func TestHandleHTTPResponseErrorNonJson(t *testing.T) {
	msg := `{"details":"requesting higher privileges than access token allows"}`
	response := &http.Response{
		Status:     "403 Forbidden",
		StatusCode: 403,
		Body:       nopCloser{bytes.NewBufferString(msg)},
	}
	err := HandleHTTPResponseError(response)

	if !strings.Contains(err.Error(), msg) {
		t.Errorf("Expected %q, got: %q", msg, err.Error())
	}
}
