package azure

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"testing"
	"time"

	"github.com/Azure/go-autorest/autorest"
	"github.com/Azure/go-autorest/autorest/mocks"
)

const (
	headerAuthorization = "Authorization"
	longDelay           = 5 * time.Second
	retryDelay          = 10 * time.Millisecond
	testLogPrefix       = "azure:"
)

// Use a Client Inspector to set the request identifier.
func ExampleWithClientID() {
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	req, _ := autorest.Prepare(&http.Request{},
		autorest.AsGet(),
		autorest.WithBaseURL("https://microsoft.com/a/b/c/"))

	c := autorest.Client{Sender: mocks.NewSender()}
	c.RequestInspector = WithReturningClientID(uuid)

	autorest.SendWithSender(c, req)
	fmt.Printf("Inspector added the %s header with the value %s\n",
		HeaderClientID, req.Header.Get(HeaderClientID))
	fmt.Printf("Inspector added the %s header with the value %s\n",
		HeaderReturnClientID, req.Header.Get(HeaderReturnClientID))
	// Output:
	// Inspector added the x-ms-client-request-id header with the value 71FDB9F4-5E49-4C12-B266-DE7B4FD999A6
	// Inspector added the x-ms-return-client-request-id header with the value true
}

func TestWithReturningClientIDReturnsError(t *testing.T) {
	var errIn error
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	_, errOut := autorest.Prepare(&http.Request{},
		withErrorPrepareDecorator(&errIn),
		WithReturningClientID(uuid))

	if errOut == nil || errIn != errOut {
		t.Fatalf("azure: WithReturningClientID failed to exit early when receiving an error -- expected (%v), received (%v)",
			errIn, errOut)
	}
}

func TestWithClientID(t *testing.T) {
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	req, _ := autorest.Prepare(&http.Request{},
		WithClientID(uuid))

	if req.Header.Get(HeaderClientID) != uuid {
		t.Fatalf("azure: WithClientID failed to set %s -- expected %s, received %s",
			HeaderClientID, uuid, req.Header.Get(HeaderClientID))
	}
}

func TestWithReturnClientID(t *testing.T) {
	b := false
	req, _ := autorest.Prepare(&http.Request{},
		WithReturnClientID(b))

	if req.Header.Get(HeaderReturnClientID) != strconv.FormatBool(b) {
		t.Fatalf("azure: WithReturnClientID failed to set %s -- expected %s, received %s",
			HeaderClientID, strconv.FormatBool(b), req.Header.Get(HeaderClientID))
	}
}

func TestExtractClientID(t *testing.T) {
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	resp := mocks.NewResponse()
	mocks.SetResponseHeader(resp, HeaderClientID, uuid)

	if ExtractClientID(resp) != uuid {
		t.Fatalf("azure: ExtractClientID failed to extract the %s -- expected %s, received %s",
			HeaderClientID, uuid, ExtractClientID(resp))
	}
}

func TestExtractRequestID(t *testing.T) {
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	resp := mocks.NewResponse()
	mocks.SetResponseHeader(resp, HeaderRequestID, uuid)

	if ExtractRequestID(resp) != uuid {
		t.Fatalf("azure: ExtractRequestID failed to extract the %s -- expected %s, received %s",
			HeaderRequestID, uuid, ExtractRequestID(resp))
	}
}

func TestIsAzureError_ReturnsTrueForAzureError(t *testing.T) {
	if !IsAzureError(&RequestError{}) {
		t.Fatalf("azure: IsAzureError failed to return true for an Azure Service error")
	}
}

func TestIsAzureError_ReturnsFalseForNonAzureError(t *testing.T) {
	if IsAzureError(fmt.Errorf("An Error")) {
		t.Fatalf("azure: IsAzureError return true for an non-Azure Service error")
	}
}

func TestNewErrorWithError_UsesReponseStatusCode(t *testing.T) {
	e := NewErrorWithError(fmt.Errorf("Error"), "packageType", "method", mocks.NewResponseWithStatus("Forbidden", http.StatusForbidden), "message")
	if e.StatusCode != http.StatusForbidden {
		t.Fatalf("azure: NewErrorWithError failed to use the Status Code of the passed Response -- expected %v, received %v", http.StatusForbidden, e.StatusCode)
	}
}

func TestNewErrorWithError_ReturnsUnwrappedError(t *testing.T) {
	e1 := RequestError{}
	e1.ServiceError = &ServiceError{Code: "42", Message: "A Message"}
	e1.StatusCode = 200
	e1.RequestID = "A RequestID"
	e2 := NewErrorWithError(&e1, "packageType", "method", nil, "message")

	if !reflect.DeepEqual(e1, e2) {
		t.Fatalf("azure: NewErrorWithError wrapped an RequestError -- expected %T, received %T", e1, e2)
	}
}

func TestNewErrorWithError_WrapsAnError(t *testing.T) {
	e1 := fmt.Errorf("Inner Error")
	var e2 interface{} = NewErrorWithError(e1, "packageType", "method", nil, "message")

	if _, ok := e2.(RequestError); !ok {
		t.Fatalf("azure: NewErrorWithError failed to wrap a standard error -- received %T", e2)
	}
}

func TestWithErrorUnlessStatusCode_NotAnAzureError(t *testing.T) {
	body := `<html>
		<head>
			<title>IIS Error page</title>
		</head>
		<body>Some non-JSON error page</body>
	</html>`
	r := mocks.NewResponseWithContent(body)
	r.Request = mocks.NewRequest()
	r.StatusCode = http.StatusBadRequest
	r.Status = http.StatusText(r.StatusCode)

	err := autorest.Respond(r,
		WithErrorUnlessStatusCode(http.StatusOK),
		autorest.ByClosing())
	ok, _ := err.(*RequestError)
	if ok != nil {
		t.Fatalf("azure: azure.RequestError returned from malformed response: %v", err)
	}

	// the error body should still be there
	defer r.Body.Close()
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		t.Fatal(err)
	}
	if string(b) != body {
		t.Fatalf("response body is wrong. got=%q exptected=%q", string(b), body)
	}
}

func TestWithErrorUnlessStatusCode_FoundAzureErrorWithoutDetails(t *testing.T) {
	j := `{
		"error": {
			"code": "InternalError",
			"message": "Azure is having trouble right now."
		}
	}`
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	r := mocks.NewResponseWithContent(j)
	mocks.SetResponseHeader(r, HeaderRequestID, uuid)
	r.Request = mocks.NewRequest()
	r.StatusCode = http.StatusInternalServerError
	r.Status = http.StatusText(r.StatusCode)

	err := autorest.Respond(r,
		WithErrorUnlessStatusCode(http.StatusOK),
		autorest.ByClosing())

	if err == nil {
		t.Fatalf("azure: returned nil error for proper error response")
	}
	azErr, ok := err.(*RequestError)
	if !ok {
		t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
	}

	expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Azure is having trouble right now.\""
	if !reflect.DeepEqual(expected, azErr.Error()) {
		t.Fatalf("azure: service error is not unmarshaled properly.\nexpected=%v\ngot=%v", expected, azErr.Error())
	}

	if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
		t.Fatalf("azure: got wrong StatusCode=%d Expected=%d", azErr.StatusCode, expected)
	}
	if expected := uuid; azErr.RequestID != expected {
		t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
	}

	_ = azErr.Error()

	// the error body should still be there
	defer r.Body.Close()
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		t.Fatal(err)
	}
	if string(b) != j {
		t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
	}

}

func TestWithErrorUnlessStatusCode_FoundAzureErrorWithDetails(t *testing.T) {
	j := `{
		"error": {
			"code": "InternalError",
			"message": "Azure is having trouble right now.",
			"details": [{"code": "conflict1", "message":"error message1"}, 
						{"code": "conflict2", "message":"error message2"}]
		}
	}`
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	r := mocks.NewResponseWithContent(j)
	mocks.SetResponseHeader(r, HeaderRequestID, uuid)
	r.Request = mocks.NewRequest()
	r.StatusCode = http.StatusInternalServerError
	r.Status = http.StatusText(r.StatusCode)

	err := autorest.Respond(r,
		WithErrorUnlessStatusCode(http.StatusOK),
		autorest.ByClosing())

	if err == nil {
		t.Fatalf("azure: returned nil error for proper error response")
	}
	azErr, ok := err.(*RequestError)
	if !ok {
		t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
	}

	if expected := "InternalError"; azErr.ServiceError.Code != expected {
		t.Fatalf("azure: wrong error code. expected=%q; got=%q", expected, azErr.ServiceError.Code)
	}
	if azErr.ServiceError.Message == "" {
		t.Fatalf("azure: error message is not unmarshaled properly")
	}
	b, _ := json.Marshal(*azErr.ServiceError.Details)
	if string(b) != `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]` {
		t.Fatalf("azure: error details is not unmarshaled properly")
	}

	if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
		t.Fatalf("azure: got wrong StatusCode=%v Expected=%d", azErr.StatusCode, expected)
	}
	if expected := uuid; azErr.RequestID != expected {
		t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
	}

	_ = azErr.Error()

	// the error body should still be there
	defer r.Body.Close()
	b, err = ioutil.ReadAll(r.Body)
	if err != nil {
		t.Fatal(err)
	}
	if string(b) != j {
		t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
	}

}

func TestWithErrorUnlessStatusCode_NoAzureError(t *testing.T) {
	j := `{
			"Status":"NotFound"
		}`
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	r := mocks.NewResponseWithContent(j)
	mocks.SetResponseHeader(r, HeaderRequestID, uuid)
	r.Request = mocks.NewRequest()
	r.StatusCode = http.StatusInternalServerError
	r.Status = http.StatusText(r.StatusCode)

	err := autorest.Respond(r,
		WithErrorUnlessStatusCode(http.StatusOK),
		autorest.ByClosing())
	if err == nil {
		t.Fatalf("azure: returned nil error for proper error response")
	}
	azErr, ok := err.(*RequestError)
	if !ok {
		t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
	}

	expected := &ServiceError{
		Code:    "Unknown",
		Message: "Unknown service error",
	}

	if !reflect.DeepEqual(expected, azErr.ServiceError) {
		t.Fatalf("azure: service error is not unmarshaled properly. expected=%q\ngot=%q", expected, azErr.ServiceError)
	}

	if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
		t.Fatalf("azure: got wrong StatusCode=%v Expected=%d", azErr.StatusCode, expected)
	}
	if expected := uuid; azErr.RequestID != expected {
		t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
	}

	_ = azErr.Error()

	// the error body should still be there
	defer r.Body.Close()
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		t.Fatal(err)
	}
	if string(b) != j {
		t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
	}

}

func TestRequestErrorString_WithError(t *testing.T) {
	j := `{
		"error": {
			"code": "InternalError",
			"message": "Conflict",
			"details": [{"code": "conflict1", "message":"error message1"}]
		}
	}`
	uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
	r := mocks.NewResponseWithContent(j)
	mocks.SetResponseHeader(r, HeaderRequestID, uuid)
	r.Request = mocks.NewRequest()
	r.StatusCode = http.StatusInternalServerError
	r.Status = http.StatusText(r.StatusCode)

	err := autorest.Respond(r,
		WithErrorUnlessStatusCode(http.StatusOK),
		autorest.ByClosing())

	if err == nil {
		t.Fatalf("azure: returned nil error for proper error response")
	}
	azErr, _ := err.(*RequestError)
	expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Conflict\" Details=[{\"code\":\"conflict1\",\"message\":\"error message1\"}]"
	if expected != azErr.Error() {
		t.Fatalf("azure: send wrong RequestError.\nexpected=%v\ngot=%v", expected, azErr.Error())
	}
}

func withErrorPrepareDecorator(e *error) autorest.PrepareDecorator {
	return func(p autorest.Preparer) autorest.Preparer {
		return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
			*e = fmt.Errorf("azure: Faux Prepare Error")
			return r, *e
		})
	}
}

func withAsyncResponseDecorator(n int) autorest.SendDecorator {
	i := 0
	return func(s autorest.Sender) autorest.Sender {
		return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
			resp, err := s.Do(r)
			if err == nil {
				if i < n {
					resp.StatusCode = http.StatusCreated
					resp.Header = http.Header{}
					resp.Header.Add(http.CanonicalHeaderKey(headerAsyncOperation), mocks.TestURL)
					i++
				} else {
					resp.StatusCode = http.StatusOK
					resp.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
				}
			}
			return resp, err
		})
	}
}

type mockAuthorizer struct{}

func (ma mockAuthorizer) WithAuthorization() autorest.PrepareDecorator {
	return autorest.WithHeader(headerAuthorization, mocks.TestAuthorizationHeader)
}

type mockFailingAuthorizer struct{}

func (mfa mockFailingAuthorizer) WithAuthorization() autorest.PrepareDecorator {
	return func(p autorest.Preparer) autorest.Preparer {
		return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
			return r, fmt.Errorf("ERROR: mockFailingAuthorizer returned expected error")
		})
	}
}

type mockInspector struct {
	wasInvoked bool
}

func (mi *mockInspector) WithInspection() autorest.PrepareDecorator {
	return func(p autorest.Preparer) autorest.Preparer {
		return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
			mi.wasInvoked = true
			return p.Prepare(r)
		})
	}
}

func (mi *mockInspector) ByInspecting() autorest.RespondDecorator {
	return func(r autorest.Responder) autorest.Responder {
		return autorest.ResponderFunc(func(resp *http.Response) error {
			mi.wasInvoked = true
			return r.Respond(resp)
		})
	}
}
