package autorest

// Copyright 2017 Microsoft Corporation
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"reflect"
	"sort"
	"strings"
	"testing"

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

const (
	jsonT = `
    {
      "name":"Rob Pike",
      "age":42
    }`
	xmlT = `<?xml version="1.0" encoding="UTF-8"?>
	<Person>
		<Name>Rob Pike</Name>
		<Age>42</Age>
	</Person>`
)

func TestNewDecoderCreatesJSONDecoder(t *testing.T) {
	d := NewDecoder(EncodedAsJSON, strings.NewReader(jsonT))
	_, ok := d.(*json.Decoder)
	if d == nil || !ok {
		t.Fatal("autorest: NewDecoder failed to create a JSON decoder when requested")
	}
}

func TestNewDecoderCreatesXMLDecoder(t *testing.T) {
	d := NewDecoder(EncodedAsXML, strings.NewReader(xmlT))
	_, ok := d.(*xml.Decoder)
	if d == nil || !ok {
		t.Fatal("autorest: NewDecoder failed to create an XML decoder when requested")
	}
}

func TestNewDecoderReturnsNilForUnknownEncoding(t *testing.T) {
	d := NewDecoder("unknown", strings.NewReader(xmlT))
	if d != nil {
		t.Fatal("autorest: NewDecoder created a decoder for an unknown encoding")
	}
}

func TestCopyAndDecodeDecodesJSON(t *testing.T) {
	_, err := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT), &mocks.T{})
	if err != nil {
		t.Fatalf("autorest: CopyAndDecode returned an error with valid JSON - %v", err)
	}
}

func TestCopyAndDecodeDecodesXML(t *testing.T) {
	_, err := CopyAndDecode(EncodedAsXML, strings.NewReader(xmlT), &mocks.T{})
	if err != nil {
		t.Fatalf("autorest: CopyAndDecode returned an error with valid XML - %v", err)
	}
}

func TestCopyAndDecodeReturnsJSONDecodingErrors(t *testing.T) {
	_, err := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT[0:len(jsonT)-2]), &mocks.T{})
	if err == nil {
		t.Fatalf("autorest: CopyAndDecode failed to return an error with invalid JSON")
	}
}

func TestCopyAndDecodeReturnsXMLDecodingErrors(t *testing.T) {
	_, err := CopyAndDecode(EncodedAsXML, strings.NewReader(xmlT[0:len(xmlT)-2]), &mocks.T{})
	if err == nil {
		t.Fatalf("autorest: CopyAndDecode failed to return an error with invalid XML")
	}
}

func TestCopyAndDecodeAlwaysReturnsACopy(t *testing.T) {
	b, _ := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT), &mocks.T{})
	if b.String() != jsonT {
		t.Fatalf("autorest: CopyAndDecode failed to return a valid copy of the data - %v", b.String())
	}
}

func TestTeeReadCloser_Copies(t *testing.T) {
	v := &mocks.T{}
	r := mocks.NewResponseWithContent(jsonT)
	b := &bytes.Buffer{}

	r.Body = TeeReadCloser(r.Body, b)

	err := Respond(r,
		ByUnmarshallingJSON(v),
		ByClosing())
	if err != nil {
		t.Fatalf("autorest: TeeReadCloser returned an unexpected error -- %v", err)
	}
	if b.String() != jsonT {
		t.Fatalf("autorest: TeeReadCloser failed to copy the bytes read")
	}
}

func TestTeeReadCloser_PassesReadErrors(t *testing.T) {
	v := &mocks.T{}
	r := mocks.NewResponseWithContent(jsonT)

	r.Body.(*mocks.Body).Close()
	r.Body = TeeReadCloser(r.Body, &bytes.Buffer{})

	err := Respond(r,
		ByUnmarshallingJSON(v),
		ByClosing())
	if err == nil {
		t.Fatalf("autorest: TeeReadCloser failed to return the expected error")
	}
}

func TestTeeReadCloser_ClosesWrappedReader(t *testing.T) {
	v := &mocks.T{}
	r := mocks.NewResponseWithContent(jsonT)

	b := r.Body.(*mocks.Body)
	r.Body = TeeReadCloser(r.Body, &bytes.Buffer{})
	err := Respond(r,
		ByUnmarshallingJSON(v),
		ByClosing())
	if err != nil {
		t.Fatalf("autorest: TeeReadCloser returned an unexpected error -- %v", err)
	}
	if b.IsOpen() {
		t.Fatalf("autorest: TeeReadCloser failed to close the nested io.ReadCloser")
	}
}

func TestContainsIntFindsValue(t *testing.T) {
	ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	v := 5
	if !containsInt(ints, v) {
		t.Fatalf("autorest: containsInt failed to find %v in %v", v, ints)
	}
}

func TestContainsIntDoesNotFindValue(t *testing.T) {
	ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	v := 42
	if containsInt(ints, v) {
		t.Fatalf("autorest: containsInt unexpectedly found %v in %v", v, ints)
	}
}

func TestContainsIntAcceptsEmptyList(t *testing.T) {
	ints := make([]int, 10)
	if containsInt(ints, 42) {
		t.Fatalf("autorest: containsInt failed to handle an empty list")
	}
}

func TestContainsIntAcceptsNilList(t *testing.T) {
	var ints []int
	if containsInt(ints, 42) {
		t.Fatalf("autorest: containsInt failed to handle an nil list")
	}
}

func TestEscapeStrings(t *testing.T) {
	m := map[string]string{
		"string": "a long string with = odd characters",
		"int":    "42",
		"nil":    "",
	}
	r := map[string]string{
		"string": "a+long+string+with+%3D+odd+characters",
		"int":    "42",
		"nil":    "",
	}
	v := escapeValueStrings(m)
	if !reflect.DeepEqual(v, r) {
		t.Fatalf("autorest: ensureValueStrings returned %v\n", v)
	}
}

func TestEnsureStrings(t *testing.T) {
	m := map[string]interface{}{
		"string": "string",
		"int":    42,
		"nil":    nil,
		"bytes":  []byte{255, 254, 253},
	}
	r := map[string]string{
		"string": "string",
		"int":    "42",
		"nil":    "",
		"bytes":  string([]byte{255, 254, 253}),
	}
	v := ensureValueStrings(m)
	if !reflect.DeepEqual(v, r) {
		t.Fatalf("autorest: ensureValueStrings returned %v\n", v)
	}
}

func ExampleString() {
	m := []string{
		"string1",
		"string2",
		"string3",
	}

	fmt.Println(String(m, ","))
	// Output: string1,string2,string3
}

func TestStringWithValidString(t *testing.T) {
	i := 123
	if got, want := String(i), "123"; got != want {
		t.Logf("got:  %q\nwant: %q", got, want)
		t.Fail()
	}
}

func TestStringWithStringSlice(t *testing.T) {
	s := []string{"string1", "string2"}
	if got, want := String(s, ","), "string1,string2"; got != want {
		t.Logf("got:  %q\nwant: %q", got, want)
		t.Fail()
	}
}

func TestStringWithEnum(t *testing.T) {
	type TestEnumType string
	s := TestEnumType("string1")
	if got, want := String(s), "string1"; got != want {
		t.Logf("got:  %q\nwant: %q", got, want)
		t.Fail()
	}
}

func TestStringWithEnumSlice(t *testing.T) {
	type TestEnumType string
	s := []TestEnumType{"string1", "string2"}
	if got, want := String(s, ","), "string1,string2"; got != want {
		t.Logf("got:  %q\nwant: %q", got, want)
		t.Fail()
	}
}

func TestStringWithIntegerSlice(t *testing.T) {
	type TestEnumType int32
	s := []TestEnumType{1, 2}
	if got, want := String(s, ","), "1,2"; got != want {
		t.Logf("got:  %q\nwant: %q", got, want)
		t.Fail()
	}
}

func ExampleAsStringSlice() {
	type TestEnumType string

	a := []TestEnumType{"value1", "value2"}
	b, _ := AsStringSlice(a)
	for _, c := range b {
		fmt.Println(c)
	}
	// Output:
	// value1
	// value2
}

func TestEncodeWithValidPath(t *testing.T) {
	s := Encode("Path", "Hello Gopher")
	if s != "Hello%20Gopher" {
		t.Fatalf("autorest: Encode method failed for valid path encoding. Got: %v; Want: %v", s, "Hello%20Gopher")
	}
}

func TestEncodeWithValidQuery(t *testing.T) {
	s := Encode("Query", "Hello Gopher")
	if s != "Hello+Gopher" {
		t.Fatalf("autorest: Encode method failed for valid query encoding. Got: '%v'; Want: 'Hello+Gopher'", s)
	}
}

func TestEncodeWithValidNotPathQuery(t *testing.T) {
	s := Encode("Host", "Hello Gopher")
	if s != "Hello Gopher" {
		t.Fatalf("autorest: Encode method failed for parameter not query or path. Got: '%v'; Want: 'Hello Gopher'", s)
	}
}

func TestMapToValues(t *testing.T) {
	m := map[string]interface{}{
		"a": "a",
		"b": 2,
	}
	v := url.Values{}
	v.Add("a", "a")
	v.Add("b", "2")
	if !isEqual(v, MapToValues(m)) {
		t.Fatalf("autorest: MapToValues method failed to return correct values - expected(%v) got(%v)", v, MapToValues(m))
	}
}

func TestMapToValuesWithArrayValues(t *testing.T) {
	m := map[string]interface{}{
		"a": []string{"a", "b"},
		"b": 2,
		"c": []int{3, 4},
	}
	v := url.Values{}
	v.Add("a", "a")
	v.Add("a", "b")
	v.Add("b", "2")
	v.Add("c", "3")
	v.Add("c", "4")

	if !isEqual(v, MapToValues(m)) {
		t.Fatalf("autorest: MapToValues method failed to return correct values - expected(%v) got(%v)", v, MapToValues(m))
	}
}

type someTempError struct{}

func (s someTempError) Error() string {
	return "temporary error"
}
func (s someTempError) Timeout() bool {
	return true
}
func (s someTempError) Temporary() bool {
	return true
}

func TestIsTemporaryNetworkErrorTrue(t *testing.T) {
	if !IsTemporaryNetworkError(someTempError{}) {
		t.Fatal("expected someTempError to be a temporary network error")
	}
	if !IsTemporaryNetworkError(errors.New("non-temporary network error")) {
		t.Fatal("expected random error to be a temporary network error")
	}
}

type someFatalError struct{}

func (s someFatalError) Error() string {
	return "fatal error"
}
func (s someFatalError) Timeout() bool {
	return false
}
func (s someFatalError) Temporary() bool {
	return false
}

func TestIsTemporaryNetworkErrorFalse(t *testing.T) {
	if IsTemporaryNetworkError(someFatalError{}) {
		t.Fatal("expected someFatalError to be a fatal network error")
	}
}

type tokenRefreshError struct{}

func (t tokenRefreshError) Error() string {
	return "empty"
}

func (t tokenRefreshError) Response() *http.Response {
	return nil
}

var _ adal.TokenRefreshError = (*tokenRefreshError)(nil)

func TestIsTokenRefreshError(t *testing.T) {
	err := errors.New("not a TRE")
	if IsTokenRefreshError(err) {
		t.Fatal("string error shouldn't be a TokenRefreshError")
	}
	err = tokenRefreshError{}
	if !IsTokenRefreshError(err) {
		t.Fatal("expected a TokenRefreshError")
	}
	err = NewError("package", "method", "failed")
	if IsTokenRefreshError(err) {
		t.Fatal("DetailedError shouldn't be a TokenRefreshError")
	}
	err = NewErrorWithError(tokenRefreshError{}, "package", "method", nil, "failed")
	if !IsTokenRefreshError(err) {
		t.Fatal("expected a wrapped TokenRefreshError")
	}
}

func isEqual(v, u url.Values) bool {
	for key, value := range v {
		if len(u[key]) == 0 {
			return false
		}
		sort.Strings(value)
		sort.Strings(u[key])
		for i := range value {
			if value[i] != u[key][i] {
				return false
			}
		}
		u.Del(key)
	}
	if len(u) > 0 {
		return false
	}
	return true
}

func doEnsureBodyClosed(t *testing.T) SendDecorator {
	return func(s Sender) Sender {
		return SenderFunc(func(r *http.Request) (*http.Response, error) {
			resp, err := s.Do(r)
			if resp != nil && resp.Body != nil && resp.Body.(*mocks.Body).IsOpen() {
				t.Fatal("autorest: Expected Body to be closed -- it was left open")
			}
			return resp, err
		})
	}
}

type mockAuthorizer struct{}

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

type mockFailingAuthorizer struct{}

func (mfa mockFailingAuthorizer) WithAuthorization() PrepareDecorator {
	return func(p Preparer) Preparer {
		return 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() PrepareDecorator {
	return func(p Preparer) Preparer {
		return PreparerFunc(func(r *http.Request) (*http.Request, error) {
			mi.wasInvoked = true
			return p.Prepare(r)
		})
	}
}

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

func withMessage(output *string, msg string) SendDecorator {
	return func(s Sender) Sender {
		return SenderFunc(func(r *http.Request) (*http.Response, error) {
			resp, err := s.Do(r)
			if err == nil {
				*output += msg
			}
			return resp, err
		})
	}
}

func withErrorRespondDecorator(e *error) RespondDecorator {
	return func(r Responder) Responder {
		return ResponderFunc(func(resp *http.Response) error {
			err := r.Respond(resp)
			if err != nil {
				return err
			}
			*e = fmt.Errorf("autorest: Faux Respond Error")
			return *e
		})
	}
}

type mockDrain struct {
	read   bool
	closed bool
}

func (md *mockDrain) Read(b []byte) (int, error) {
	md.read = true
	b = append(b, 0xff)
	return 1, io.EOF
}

func (md *mockDrain) Close() error {
	md.closed = true
	return nil
}

func TestDrainResponseBody(t *testing.T) {
	err := DrainResponseBody(nil)
	if err != nil {
		t.Fatalf("expected nil error, got %v", err)
	}
	err = DrainResponseBody(&http.Response{})
	if err != nil {
		t.Fatalf("expected nil error, got %v", err)
	}
	md := &mockDrain{}
	err = DrainResponseBody(&http.Response{Body: md})
	if err != nil {
		t.Fatalf("expected nil error, got %v", err)
	}
	if !md.closed {
		t.Fatal("mockDrain wasn't closed")
	}
	if !md.read {
		t.Fatal("mockDrain wasn't read")
	}
}
