package grequests

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/xml"
	"errors"
	"io"
	"net/http"
	"net/http/cookiejar"
	"net/http/httptest"
	"net/url"
	"os"
	"testing"
	"time"
)

type BasicGetResponse struct {
	Args    struct{} `json:"args"`
	Headers struct {
		Accept         string `json:"Accept"`
		AcceptEncoding string `json:"Accept-Encoding"`
		AcceptLanguage string `json:"Accept-Language"`
		Dnt            string `json:"Dst"`
		Host           string `json:"Host"`
		UserAgent      string `json:"User-Agent"`
	} `json:"headers"`
	Origin string `json:"origin"`
	URL    string `json:"url"`
}

type BasicGetResponseNewHeader struct {
	Args    struct{} `json:"args"`
	Headers struct {
		Accept           string `json:"Accept"`
		AcceptEncoding   string `json:"Accept-Encoding"`
		AcceptLanguage   string `json:"Accept-Language"`
		Dnt              string `json:"Dst"`
		Host             string `json:"Host"`
		UserAgent        string `json:"User-Agent"`
		XWonderfulHeader string `json:"X-Wonderful-Header"`
	} `json:"headers"`
	Origin string `json:"origin"`
	URL    string `json:"url"`
}

type BasicGetResponseBasicAuth struct {
	Args    struct{} `json:"args"`
	Headers struct {
		Accept         string `json:"Accept"`
		AcceptEncoding string `json:"Accept-Encoding"`
		AcceptLanguage string `json:"Accept-Language"`
		Dnt            string `json:"Dst"`
		Host           string `json:"Host"`
		UserAgent      string `json:"User-Agent"`
		Authorization  string `json:"Authorization"`
	} `json:"headers"`
	Origin string `json:"origin"`
	URL    string `json:"url"`
}

type BasicGetResponseArgs struct {
	Args struct {
		Goodbye string `json:"Goodbye"`
		Hello   string `json:"Hello"`
	} `json:"args"`
	Headers struct {
		Accept         string `json:"Accept"`
		AcceptEncoding string `json:"Accept-Encoding"`
		AcceptLanguage string `json:"Accept-Language"`
		Dnt            string `json:"Dst"`
		Host           string `json:"Host"`
		UserAgent      string `json:"User-Agent"`
		Authorization  string `json:"Authorization"`
	} `json:"headers"`
	Origin string `json:"origin"`
	URL    string `json:"url"`
}

type GetXMLSample struct {
	XMLName xml.Name `xml:"slideshow"`
	Title   string   `xml:"title,attr"`
	Date    string   `xml:"date,attr"`
	Author  string   `xml:"author,attr"`
	Slide   []struct {
		Type  string `xml:"type,attr"`
		Title string `xml:"title"`
	} `xml:"slide"`
}

type TestJSONCookies struct {
	Cookies struct {
		AnotherCookie string `json:"AnotherCookie"`
		TestCookie    string `json:"TestCookie"`
	} `json:"cookies"`
}

type MassiveJSONBlob struct {
	Type     string `json:"type"`
	Features []struct {
		Type       string `json:"type"`
		Properties struct {
			MAPBLKLOT string      `json:"MAPBLKLOT"`
			BLKLOT    string      `json:"BLKLOT"`
			BLOCKNUM  string      `json:"BLOCK_NUM"`
			LOTNUM    string      `json:"LOT_NUM"`
			FROMST    string      `json:"FROM_ST"`
			TOST      string      `json:"TO_ST"`
			STREET    string      `json:"STREET"`
			STTYPE    interface{} `json:"ST_TYPE"`
			ODDEVEN   string      `json:"ODD_EVEN"`
		} `json:"properties"`
		Geometry struct {
			Type        string `json:"type"`
			Coordinates []struct {
				Num0  []float64 `json:"0,omitempty"`
				Num1  []float64 `json:"1,omitempty"`
				Num2  []float64 `json:"2,omitempty"`
				Num3  []float64 `json:"3,omitempty"`
				Num4  []float64 `json:"4,omitempty"`
				Num5  []float64 `json:"5,omitempty"`
				Num6  []float64 `json:"6,omitempty"`
				Num7  []float64 `json:"7,omitempty"`
				Num8  []float64 `json:"8,omitempty"`
				Num9  []float64 `json:"9,omitempty"`
				Num10 []float64 `json:"10,omitempty"`
			} `json:"-"`
		} `json:"geometry"`
	} `json:"features"`
}

type GithubSelfJSON struct {
	ID       int    `json:"id"`
	Name     string `json:"name"`
	FullName string `json:"full_name"`
	Owner    struct {
		Login             string `json:"login"`
		ID                int    `json:"id"`
		AvatarURL         string `json:"avatar_url"`
		GravatarID        string `json:"gravatar_id"`
		URL               string `json:"url"`
		HTMLURL           string `json:"html_url"`
		FollowersURL      string `json:"followers_url"`
		FollowingURL      string `json:"following_url"`
		GistsURL          string `json:"gists_url"`
		StarredURL        string `json:"starred_url"`
		SubscriptionsURL  string `json:"subscriptions_url"`
		OrganizationsURL  string `json:"organizations_url"`
		ReposURL          string `json:"repos_url"`
		EventsURL         string `json:"events_url"`
		ReceivedEventsURL string `json:"received_events_url"`
		Type              string `json:"type"`
		SiteAdmin         bool   `json:"site_admin"`
	} `json:"owner"`
	Private          bool        `json:"private"`
	HTMLURL          string      `json:"html_url"`
	Description      string      `json:"description"`
	Fork             bool        `json:"fork"`
	URL              string      `json:"url"`
	ForksURL         string      `json:"forks_url"`
	KeysURL          string      `json:"keys_url"`
	CollaboratorsURL string      `json:"collaborators_url"`
	TeamsURL         string      `json:"teams_url"`
	HooksURL         string      `json:"hooks_url"`
	IssueEventsURL   string      `json:"issue_events_url"`
	EventsURL        string      `json:"events_url"`
	AssigneesURL     string      `json:"assignees_url"`
	BranchesURL      string      `json:"branches_url"`
	TagsURL          string      `json:"tags_url"`
	BlobsURL         string      `json:"blobs_url"`
	GitTagsURL       string      `json:"git_tags_url"`
	GitRefsURL       string      `json:"git_refs_url"`
	TreesURL         string      `json:"trees_url"`
	StatusesURL      string      `json:"statuses_url"`
	LanguagesURL     string      `json:"languages_url"`
	StargazersURL    string      `json:"stargazers_url"`
	ContributorsURL  string      `json:"contributors_url"`
	SubscribersURL   string      `json:"subscribers_url"`
	SubscriptionURL  string      `json:"subscription_url"`
	CommitsURL       string      `json:"commits_url"`
	GitCommitsURL    string      `json:"git_commits_url"`
	CommentsURL      string      `json:"comments_url"`
	IssueCommentURL  string      `json:"issue_comment_url"`
	ContentsURL      string      `json:"contents_url"`
	CompareURL       string      `json:"compare_url"`
	MergesURL        string      `json:"merges_url"`
	ArchiveURL       string      `json:"archive_url"`
	DownloadsURL     string      `json:"downloads_url"`
	IssuesURL        string      `json:"issues_url"`
	PullsURL         string      `json:"pulls_url"`
	MilestonesURL    string      `json:"milestones_url"`
	NotificationsURL string      `json:"notifications_url"`
	LabelsURL        string      `json:"labels_url"`
	ReleasesURL      string      `json:"releases_url"`
	DeploymentsURL   string      `json:"deployments_url"`
	CreatedAt        time.Time   `json:"created_at"`
	UpdatedAt        time.Time   `json:"updated_at"`
	PushedAt         time.Time   `json:"pushed_at"`
	GitURL           string      `json:"git_url"`
	SSHURL           string      `json:"ssh_url"`
	CloneURL         string      `json:"clone_url"`
	SvnURL           string      `json:"svn_url"`
	Homepage         string      `json:"homepage"`
	Size             int         `json:"size"`
	StargazersCount  int         `json:"stargazers_count"`
	WatchersCount    int         `json:"watchers_count"`
	Language         string      `json:"language"`
	HasIssues        bool        `json:"has_issues"`
	HasDownloads     bool        `json:"has_downloads"`
	HasWiki          bool        `json:"has_wiki"`
	HasPages         bool        `json:"has_pages"`
	ForksCount       int         `json:"forks_count"`
	MirrorURL        interface{} `json:"mirror_url"`
	OpenIssuesCount  int         `json:"open_issues_count"`
	Forks            int         `json:"forks"`
	OpenIssues       int         `json:"open_issues"`
	Watchers         int         `json:"watchers"`
	DefaultBranch    string      `json:"default_branch"`
	NetworkCount     int         `json:"network_count"`
	SubscribersCount int         `json:"subscribers_count"`
}

func TestGetNoOptions(t *testing.T) {
	resp, _ := Get("http://httpbin.org/get", nil)
	verifyOkResponse(resp, t)
}

func TestGetNoOptionsCustomClient(t *testing.T) {
	resp, _ := Get("http://httpbin.org/get",
		&RequestOptions{HTTPClient: http.DefaultClient})
	verifyOkResponse(resp, t)
}

func TestGetCustomTLSHandshakeTimeout(t *testing.T) {
	ro := &RequestOptions{TLSHandshakeTimeout: 10 * time.Millisecond}
	if _, err := Get("https://httpbin.org", ro); err == nil {
		t.Error("unexpected: successful TLS Handshake")
	}
}

func TestGetCustomDialTimeout(t *testing.T) {
	ro := &RequestOptions{DialTimeout: time.Nanosecond}
	if _, err := Get("http://httpbin.org", ro); err == nil {
		t.Error("unexpected: successful connection")
	}
}

func TestGetProxy(t *testing.T) {
	ch := make(chan string, 1)

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ch <- "real server"
	}))

	defer ts.Close()

	proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ch <- "proxy for " + r.URL.String()
	}))

	defer proxy.Close()

	pu, err := url.Parse(proxy.URL)
	if err != nil {
		t.Fatal(err)
	}

	resp, err := Head(ts.URL, &RequestOptions{Proxies: map[string]*url.URL{pu.Scheme: pu}})

	defer http.DefaultTransport.(*http.Transport).CloseIdleConnections()

	if err != nil {
		t.Error("Unable to make request: ", err)
	}

	if resp.Ok != true {
		t.Error("Response is not OK for some reason: ", resp.StatusCode)
	}

	got := <-ch
	want := "proxy for " + ts.URL + "/"
	if got != want {
		t.Errorf("want %q, got %q", want, got)
	}
}

func TestGetSyncInvalidProxyScheme(t *testing.T) {
	resp, err := Get("http://httpbin.org/get", &RequestOptions{Proxies: map[string]*url.URL{"gopher": nil}})
	if err != nil {
		t.Error("Request failed: ", err)
	}

	verifyOkResponse(resp, t)
}

func TestGetSyncNoOptions(t *testing.T) {
	resp, err := Get("http://httpbin.org/get", nil)
	if err != nil {
		t.Error("Request failed: ", err)
	}

	verifyOkResponse(resp, t)
}

func TestGetNoOptionsGzip(t *testing.T) {
	resp, _ := Get("https://httpbin.org/gzip", nil)
	verifyOkResponse(resp, t)
}

func TestGetWithCookies(t *testing.T) {
	resp, err := Get("http://httpbin.org/cookies",
		&RequestOptions{
			Cookies: []*http.Cookie{
				{
					Name:     "TestCookie",
					Value:    "Random Value",
					HttpOnly: true,
					Secure:   false,
				}, {
					Name:     "AnotherCookie",
					Value:    "Some Value",
					HttpOnly: true,
					Secure:   false,
				},
			},
		})

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	myJSONStruct := &TestJSONCookies{}

	if err := resp.JSON(myJSONStruct); err != nil {
		t.Error("Cannot serialize cookie JSON: ", err)
	}

	if myJSONStruct.Cookies.TestCookie != "Random Value" {
		t.Errorf("Cookie value not set properly: %#v", myJSONStruct)
	}

	if myJSONStruct.Cookies.AnotherCookie != "Some Value" {
		t.Errorf("Cookie value not set properly: %#v", myJSONStruct)
	}

}

func TestGetWithCookiesCustomCookieJar(t *testing.T) {
	cookieJar, _ := cookiejar.New(nil)
	resp, err := Get("http://httpbin.org/cookies",
		&RequestOptions{
			CookieJar: cookieJar,
			Cookies: []*http.Cookie{
				{
					Name:     "TestCookie",
					Value:    "Random Value",
					HttpOnly: true,
					Secure:   false,
				}, {
					Name:     "AnotherCookie",
					Value:    "Some Value",
					HttpOnly: true,
					Secure:   false,
				},
			},
		})

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	myJSONStruct := &TestJSONCookies{}

	if err := resp.JSON(myJSONStruct); err != nil {
		t.Error("Cannot serialize cookie JSON: ", err)
	}

	if myJSONStruct.Cookies.TestCookie != "Random Value" {
		t.Errorf("Cookie value not set properly: %#v", myJSONStruct)
	}

	if myJSONStruct.Cookies.AnotherCookie != "Some Value" {
		t.Errorf("Cookie value not set properly: %#v", myJSONStruct)
	}

}

func TestGetSession(t *testing.T) {
	session := NewSession(nil)

	resp, err := session.Get("http://httpbin.org/cookies/set", &RequestOptions{Params: map[string]string{"one": "two"}})

	if err != nil {
		t.Fatal("Cannot set cookie: ", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	resp, err = session.Get("http://httpbin.org/cookies/set", &RequestOptions{Params: map[string]string{"two": "three"}})

	if err != nil {
		t.Fatal("Cannot set cookie: ", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	resp, err = session.Get("http://httpbin.org/cookies/set", &RequestOptions{Params: map[string]string{"three": "four"}})

	if err != nil {
		t.Fatal("Cannot set cookie: ", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	cookieURL, err := url.Parse("http://httpbin.org")
	if err != nil {
		t.Error("We (for some reason) cannot parse the cookie URL")
	}

	if len(session.HTTPClient.Jar.Cookies(cookieURL)) != 3 {
		t.Error("Invalid number of cookies provided: ", session.HTTPClient.Jar.Cookies(cookieURL))
	}

	for _, cookie := range session.HTTPClient.Jar.Cookies(cookieURL) {
		switch cookie.Name {
		case "one":
			if cookie.Value != "two" {
				t.Error("Cookie value is not valid", cookie)
			}
		case "two":
			if cookie.Value != "three" {
				t.Error("Cookie value is not valid", cookie)
			}
		case "three":
			if cookie.Value != "four" {
				t.Error("Cookie value is not valid", cookie)
			}
		default:
			t.Error("We should not have any other cookies: ", cookie)
		}
	}

	session.CloseIdleConnections()

}

//func TestGetNoOptionsDeflate(t *testing.T) {
//	verifyOkResponse(<-GetAsync("http://httpbin.org/deflate", nil), t)
//}

func xmlASCIIDecoder(charset string, input io.Reader) (io.Reader, error) {
	return input, nil
}

func TestGetInvalidURL(t *testing.T) {
	resp, err := Get("%../dir/", &RequestOptions{Params: map[string]string{"1": "2"}})

	if err == nil {
		t.Error("Some how the request was valid to make request", err)
	}

	resp.ClearInternalBuffer() // This will panic without our nil checks
}

func TestGetInvalidURLNoParams(t *testing.T) {
	_, err := Get("%../dir/", nil)

	if err == nil {
		t.Error("Some how the request was valid to make request", err)
	}
}

func TestGetInvalidURLSession(t *testing.T) {
	session := NewSession(nil)

	if _, err := session.Get("%../dir/", nil); err == nil {
		t.Error("Some how the request was valid to make request ", err)
	}
}

func TestGetXMLSerialize(t *testing.T) {
	resp, err := Get("http://httpbin.org/xml", nil)

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	userXML := &GetXMLSample{}

	if err := resp.XML(userXML, xmlASCIIDecoder); err != nil {
		t.Error("Unable to consume the response as XML: ", err)
	}

	if userXML.Title != "Sample Slide Show" {
		t.Errorf("Invalid XML serialization %#v", userXML)
	}

	if err := resp.XML(int(123), nil); err == nil {
		t.Error("Still able to consume XML from used response")
	}

}

func TestGetCustomUserAgent(t *testing.T) {
	ro := &RequestOptions{UserAgent: "LeviBot 0.1"}
	resp, _ := Get("http://httpbin.org/get", ro)
	jsonResp := verifyOkResponse(resp, t)
	if jsonResp.Headers.UserAgent != "LeviBot 0.1" {
		t.Error("User agent header not properly set")
	}
}

func TestGetBasicAuth(t *testing.T) {
	ro := &RequestOptions{Auth: []string{"Levi", "Bot"}}
	resp, err := Get("http://httpbin.org/get", ro)
	// Not the usual JSON so copy and paste from below

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	myJSONStruct := &BasicGetResponseBasicAuth{}

	err = resp.JSON(myJSONStruct)
	if err != nil {
		t.Error("Unable to coerce to JSON", err)
	}

	if myJSONStruct.Headers.Authorization != "Basic TGV2aTpCb3Q=" {
		t.Error("Unable to set HTTP basic auth", myJSONStruct.Headers)
	}

}

func TestGetCustomHeader(t *testing.T) {
	ro := &RequestOptions{UserAgent: "LeviBot 0.1",
		Headers: map[string]string{"X-Wonderful-Header": "1"}}
	resp, err := Get("http://httpbin.org/get", ro)
	// Not the usual JSON so copy and paste from below

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	myJSONStruct := &BasicGetResponseNewHeader{}

	err = resp.JSON(myJSONStruct)
	if err != nil {
		t.Error("Unable to coerce to JSON", err)
	}

	if myJSONStruct.Headers.XWonderfulHeader != "1" {
		t.Error("Unable to set custom HTTP header", myJSONStruct.Headers)
	}
}

func TestGetInvalidSSLCertNoVerify(t *testing.T) {
	ro := &RequestOptions{InsecureSkipVerify: true}
	for _, badSSL := range []string{
		"https://self-signed.badssl.com/",
		"https://expired.badssl.com/",
		"https://wrong.host.badssl.com/",
	} {
		resp, err := Get(badSSL, ro)
		if err != nil {
			t.Error("Unable to make request", err)
		}
		if resp.Ok != true {
			t.Error("Request did not return OK")
		}
	}

}

func TestGetInvalidSSLCertNoVerifyNoOptions(t *testing.T) {
	for _, badSSL := range []string{
		"https://self-signed.badssl.com/",
		"https://expired.badssl.com/",
		"https://wrong.host.badssl.com/",
	} {
		resp, err := Get(badSSL, nil)
		if err == nil {
			t.Error("Unable to make request", err)
		}

		if resp.Ok == true {
			t.Error("Request did not return OK")
		}
	}
}

func TestGetInvalidSSLCertNoCompression(t *testing.T) {
	ro := &RequestOptions{UserAgent: "LeviBot 0.1", DisableCompression: true}
	resp, err := Get("https://self-signed.badssl.com/", ro)

	if err == nil {
		t.Error("SSL verification worked when it shouldn't of", err)
	}

	if resp.Ok == true {
		t.Error("Request did return OK")
	}

}

func TestGetInvalidSSLCertWithCompression(t *testing.T) {
	ro := &RequestOptions{UserAgent: "LeviBot 0.1", DisableCompression: false}
	resp, err := Get("https://self-signed.badssl.com/", ro)

	if err == nil {
		t.Error("SSL verification worked when it shouldn't of", err)
	}

	if resp.Ok == true {
		t.Error("Request did return OK")
	}

}

func TestErrorResponseNOOP(t *testing.T) {
	ro := &RequestOptions{UserAgent: "LeviBot 0.1", DisableCompression: false}
	resp, err := Get("https://self-signed.badssl.com/", ro)

	if err == nil {
		t.Error("SSL verification worked when it shouldn't of", err)
	}

	if resp.Ok == true {
		t.Error("Request did return OK")
	}

	myJSONStruct := &BasicGetResponseArgs{}

	if err := resp.JSON(myJSONStruct); err == nil {
		t.Error("Somehow Able to convert to JSON", err)
	}

	if resp.Bytes() != nil {
		t.Error("Somehow byte buffer is working now (bytes)", resp.Bytes())
	}

	if resp.String() != "" {
		t.Error("Somehow byte buffer is working now (bytes)", resp.String())
	}

	resp.ClearInternalBuffer()

	if resp.Bytes() != nil {
		t.Error("Somehow byte buffer is working now (bytes)", resp.Bytes())
	}

	if resp.String() != "" {
		t.Error("Somehow byte buffer is working now (bytes)", resp.String())
	}

	userXML := &GetXMLSample{}

	if err := resp.XML(userXML, xmlASCIIDecoder); err == nil {
		t.Errorf("Somehow to consume the response as XML: %#v", userXML)
	}

	fileName := "randomFile"

	if err := resp.DownloadToFile(fileName); err == nil {
		t.Error("Somehow able to download to file: ", err)
	}

	var buf [1]byte

	if written, err := resp.Read(buf[:]); written != -1 && err == nil {
		t.Error("Somehow we were able to read from our error response")
	}

}

func TestGetInvalidSSLCertNoCompressionNoVerify(t *testing.T) {
	ro := &RequestOptions{UserAgent: "LeviBot 0.1", InsecureSkipVerify: true, DisableCompression: true}
	resp, err := Get("https://self-signed.badssl.com/", ro)

	if err != nil {
		t.Error("SSL verification worked when it shouldn't of", err)
	}

	if resp.Ok != true {
		t.Error("Request did return OK")
	}

}

func TestGetInvalidSSLCertWithCompressionNoVerify(t *testing.T) {
	ro := &RequestOptions{UserAgent: "LeviBot 0.1", InsecureSkipVerify: true, DisableCompression: false}
	resp, err := Get("https://self-signed.badssl.com/", ro)

	if err != nil {
		t.Error("SSL verification worked when it shouldn't of", err)
	}

	if resp.Ok != true {
		t.Error("Request did return OK")
	}

}

func TestGetInvalidSSLCert(t *testing.T) {
	ro := &RequestOptions{UserAgent: "LeviBot 0.1"}
	resp, err := Get("https://self-signed.badssl.com/", ro)

	if err == nil {
		t.Error("SSL verification worked when it shouldn't of", err)
	}

	if resp.Ok == true {
		t.Error("Request did return OK")
	}

}

func TestGetBasicArgs(t *testing.T) {
	ro := &RequestOptions{
		Params: map[string]string{"Hello": "World"},
	}
	resp, _ := Get("http://httpbin.org/get?Goodbye=World", ro)

	verifyOkArgsResponse(resp, t)

}

func TestGetBasicArgsQueryStruct(t *testing.T) {
	ro := &RequestOptions{
		QueryStruct: struct {
			Hello string `url:"Hello"`
		}{
			"World",
		},
	}
	resp, _ := Get("http://httpbin.org/get?Goodbye=World", ro)

	verifyOkArgsResponse(resp, t)

}

func TestGetBasicArgsQueryStructErr(t *testing.T) {
	ro := &RequestOptions{
		QueryStruct: 5,
	}
	resp, err := Get("http://httpbin.org/get?Goodbye=World", ro)

	if err == nil {
		t.Error("URL Parsing should have failed")
	}

	if resp.Ok == true {
		t.Error("Request did return OK")
	}

}

func TestGetBasicArgsQueryStructUrlQueryErr(t *testing.T) {
	ro := &RequestOptions{
		QueryStruct: 5,
	}
	resp, err := Get("http://httpbin.org/get?Goodbye=World%zz", ro)

	if err == nil {
		t.Error("URL Parsing should have failed")
	}

	if resp.Ok == true {
		t.Error("Request did return OK")
	}

}

func TestGetBasicArgsQueryStructUrlErr(t *testing.T) {
	ro := &RequestOptions{
		QueryStruct: 5,
	}
	resp, err := Get("%", ro)

	if err == nil {
		t.Error("URL Parsing should have failed")
	}

	if resp.Ok == true {
		t.Error("Request did return OK")
	}

}

func TestGetBasicArgsErr(t *testing.T) {
	ro := &RequestOptions{
		Params: map[string]string{"Hello": "World"},
	}
	resp, err := Get("http://httpbin.org/get?Goodbye=%zzz", ro)

	if err == nil {
		t.Error("URL Parsing should have failed")
	}

	if resp.Ok == true {
		t.Error("Request did return OK")
	}

}

func TestGetBasicArgsParams(t *testing.T) {
	ro := &RequestOptions{
		Params: map[string]string{"Hello": "World", "Goodbye": "World"},
	}
	resp, _ := Get("http://httpbin.org/get", ro)

	verifyOkArgsResponse(resp, t)
}

func TestGetBasicArgsParamsOverwrite(t *testing.T) {
	ro := &RequestOptions{
		Params: map[string]string{"Hello": "World", "Goodbye": "World"},
	}

	resp, _ := Get("http://httpbin.org/get?Hello=Nothing", ro)

	verifyOkArgsResponse(resp, t)
}

func TestGetFileDownload(t *testing.T) {
	resp, err := Get("http://httpbin.org/get", nil)

	fileName := "randomFile"

	if err := resp.DownloadToFile(fileName); err != nil {
		t.Error("Unable to download to file: ", err)
	}

	if err := resp.DownloadToFile("."); err == nil {
		t.Error("Able to create file '.'")
	}

	fd, err := os.Open(fileName)
	defer fd.Close()
	defer os.Remove(fileName)

	if err != nil {
		t.Error("Unable to open file to verify content ", err)
	}

	jsonDecoder := json.NewDecoder(fd)

	myJSONStruct := &BasicGetResponse{}

	if err := jsonDecoder.Decode(myJSONStruct); err != nil {
		t.Error("Unable to cocerce file to JSON ", err)
	}

	if myJSONStruct.URL != "http://httpbin.org/get" {
		t.Error("For some reason the URL isn't the same", myJSONStruct.URL)
	}

	if myJSONStruct.Headers.Host != "httpbin.org" {
		t.Error("The host header is invalid")
	}

	if resp.Bytes() != nil {
		t.Error("JSON decoding did not fully consume the response stream (Bytes)", resp.Bytes())
	}

	if resp.String() != "" {
		t.Error("JSON decoding did not fully consume the response stream (String)", resp.String())
	}

	if resp.StatusCode != 200 {
		t.Error("Response returned a non-200 code")
	}

}

func TestJsonConsumedResponse(t *testing.T) {
	resp, err := Get("http://httpbin.org/get", nil)

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	if resp.Bytes() == nil {
		t.Error("Unable to coerce value to bytes", resp.Bytes())
	}

	resp.ClearInternalBuffer()

	if err := resp.JSON(struct{}{}); err == nil {
		t.Error("Struct should not be able to hold JSON: ")
	}
}

func TestDownloadConsumedResponse(t *testing.T) {
	resp, err := Get("http://httpbin.org/get", nil)

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	if resp.Bytes() == nil {
		t.Error("Unable to coerce value to bytes")
	}

	resp.ClearInternalBuffer()

	if err := resp.DownloadToFile("randomFile"); err == nil {
		t.Error("Still able to download file: ", err)
	}

	defer os.Remove("randomFile")
}

func TestGetBytes(t *testing.T) {
	resp, err := Get("http://httpbin.org/get", nil)

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	if resp.Bytes() == nil {
		t.Error("JSON decoding did not fully consume the response stream")
	}

	if bytes.Compare(resp.Bytes(), resp.Bytes()) != 0 {
		t.Error("Body bytes have not been cached", resp.Bytes())
	}
}

func TestGetBytesNoBuffer(t *testing.T) {
	resp, err := Get("http://httpbin.org/get", nil)

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	if resp.Bytes() == nil {
		t.Error("Cannot coerce HTTP response to bytes")
	}

	if bytes.Compare(resp.Bytes(), resp.Bytes()) != 0 {
		t.Error("Body bytes have not been cached", resp.Bytes())
	}

	if err := resp.DownloadToFile("randomFile"); err != nil {
		t.Error("Unable to download file: ", err)
	}

	defer os.Remove("randomFile")

	resp.ClearInternalBuffer()

	if resp.Bytes() != nil {
		t.Error("Internal Buffer not cleaned up")
	}
}

func TestGetString(t *testing.T) {
	resp, err := Get("http://httpbin.org/get", nil)

	if err != nil {
		t.Error("Unable to make request", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	if resp.String() == "" {
		t.Error("Response Stream not returned as string", resp.String())
	}

	if resp.String() != resp.String() {
		t.Error("Body string have not been cached", resp.String())
	}

	if err := resp.DownloadToFile("randomFile"); err != nil {
		t.Error("Unable to download file: ", err)
	}

	defer os.Remove("randomFile")

	resp.ClearInternalBuffer()

	if resp.String() != "" {
		t.Error("Internal Buffer not cleaned up")
	}

}

func TestGetRedirectHeaderCopy(t *testing.T) {
	srv := httptest.NewServer(http.DefaultServeMux)
	http.HandleFunc("/foo", func(w http.ResponseWriter, req *http.Request) {
		if req.Header.Get("X-Custom") == "" {
			http.Error(w, "no custom header found", http.StatusBadRequest)
			return
		}
	})
	resp, err := Get(srv.URL+"/foo", &RequestOptions{Headers: map[string]string{"X-Custom": "1"}})

	if err != nil {
		t.Error("Redirect request failed", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	srv.Close()

}

func TestGetRedirectSecretHeaderNoCopy(t *testing.T) {
	srv := httptest.NewServer(http.DefaultServeMux)
	http.HandleFunc("/sec", func(w http.ResponseWriter, req *http.Request) {
		if req.Header.Get("X-Custom") == "" {
			http.Error(w, "no custom header found", http.StatusBadRequest)
			return
		}
	})
	resp, err := Get(srv.URL+"/sec", &RequestOptions{
		Headers: map[string]string{"X-Custom": "1"}, SensitiveHTTPHeaders: map[string]struct{}{"X-Custom": {}},
	})

	if err != nil {
		t.Error("Redirect request failed", err)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	srv.Close()

}

func TestMassiveJSONFile(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping massive JSON file download because short was called")
	}
	resp, err := Get("https://raw.githubusercontent.com/levigross/sf-city-lots-json/master/citylots.json", nil)
	if err != nil {
		t.Error("Request to massive JSON blob failed", err)
	}

	myjson := &MassiveJSONBlob{}

	if err := resp.JSON(myjson); err != nil {
		t.Error("Unable to serialize massive JSON blob", err)
	}

	if myjson.Type != "FeatureCollection" {
		t.Error("JSON did not properly serialize")
	}
}

func TestGitHubSelfJSON(t *testing.T) {
	resp, err := Get("https://api.github.com/repos/levigross/grequests", nil)
	if err != nil {
		t.Error("Request to reddit JSON blob failed", err)
	}

	myjson := &GithubSelfJSON{}

	if err := resp.JSON(myjson); err != nil {
		t.Error("Unable to serialize reddit JSON blob", err)
	}
}

func TestUnlimitedRedirects(t *testing.T) {
	srv := httptest.NewServer(http.DefaultServeMux)
	http.HandleFunc("/bar", func(w http.ResponseWriter, req *http.Request) {
		http.Redirect(w, req, "/bar", http.StatusMovedPermanently)
	})

	resp, err := Get(srv.URL+"/bar", &RequestOptions{Headers: map[string]string{"X-Custom": "1"}})

	if err == nil {
		t.Error("Redirect limitation failed", err)
	}

	if resp.Ok == true {
		t.Error("Request did not returned")
	}

	srv.Close()
}

func TestAuthStripOnRedirect(t *testing.T) {
	t.SkipNow()
	srv := httptest.NewServer(http.DefaultServeMux)
	http.HandleFunc("/test/", func(w http.ResponseWriter, req *http.Request) {
		if req.Header.Get("Authorization") != "" {
			http.Error(w, "Found Auth: "+req.Header.Get("Authorization"), http.StatusInternalServerError)
			return
		}

		if req.Header.Get("WWW-Authenticate") != "" {
			http.Error(w, "Found Auth: "+req.Header.Get("WWW-Authenticate"), http.StatusInternalServerError)
			return
		}

		if req.Header.Get("Proxy-Authorization") != "" {
			http.Error(w, "Found Auth: "+req.Header.Get("Proxy-Authorization"), http.StatusInternalServerError)
			return
		}

		io.WriteString(w, "OK")
	})

	resp, err := Get(srv.URL+"/test", &RequestOptions{
		Auth:    []string{"one ", "two"},
		Headers: map[string]string{"WWW-Authenticate": "foo", "Proxy-Authorization": "bar"},
	})

	if err != nil {
		t.Error("Request had creds inside", err)
	}

	if resp.Ok != true {
		t.Error("Request had creds inside", resp.StatusCode, resp.String())
	}

	srv.Close()
}

func TestNoRedirect(t *testing.T) {
	srv := httptest.NewServer(http.DefaultServeMux)
	http.HandleFunc("/3tester/", func(w http.ResponseWriter, req *http.Request) {
		http.Redirect(w, req, "/", http.StatusMovedPermanently)
	})

	client := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return errors.New("cancel redirection")
		},
	}

	_, err := Get(srv.URL+"/3tester/", &RequestOptions{
		HTTPClient: client,
	})

	if err == nil {
		t.Error("Request passed when it was supposed to fail on redirect", err)
	}

	srv.Close()

}

func verifyOkArgsResponse(resp *Response, t *testing.T) *BasicGetResponseArgs {
	if resp.Error != nil {
		t.Error("Unable to make request", resp.Error)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	myJSONStruct := &BasicGetResponseArgs{}

	if err := resp.JSON(myJSONStruct); err != nil {
		t.Error("Unable to coerce to JSON", err)
	}

	if myJSONStruct.Args.Goodbye != "World" && myJSONStruct.Args.Hello != "World" {
		t.Error("Args not properly set", myJSONStruct.Args)
	}

	if myJSONStruct.URL != "http://httpbin.org/get?Goodbye=World&Hello=World" {
		t.Error("Url is not properly constructed", myJSONStruct.URL)
	}

	if resp.Bytes() != nil {
		t.Error("JSON decoding did not fully consume the response stream (Bytes)", resp.Bytes())
	}

	if resp.String() != "" {
		t.Error("JSON decoding did not fully consume the response stream (String)", resp.String())
	}

	if resp.StatusCode != 200 {
		t.Error("Response returned a non-200 code")
	}

	return myJSONStruct
}

func TestGetCustomRequestTimeout(t *testing.T) {
	ro := &RequestOptions{RequestTimeout: 2 * time.Nanosecond}
	if _, err := Get("http://httpbin.org", ro); err == nil {
		t.Error("unexpected: successful connection")
	}
}

func TestGetCustomRequestTimeoutContext(t *testing.T) {
	derContext := context.Background()
	ctx, cancel := context.WithTimeout(derContext, time.Microsecond)
	ro := &RequestOptions{Context: ctx}
	cancel()
	if _, err := Get("http://httpbin.org", ro); err == nil {
		t.Error("unexpected: successful connection")
	}
}

// verifyResponse will verify the following conditions
// 1. The request didn't return an error
// 2. The response returned an OK (a status code within the 200 range)
// 3. The output can be coerced to JSON (this may change later)
// It should only be run when testing GET request to http://httpbin.org/get expecting JSON
func verifyOkResponse(resp *Response, t *testing.T) *BasicGetResponse {
	if resp.Error != nil {
		t.Error("Unable to make request", resp.Error)
	}

	if resp.Ok != true {
		t.Error("Request did not return OK")
	}

	myJSONStruct := &BasicGetResponse{}

	if err := resp.JSON(myJSONStruct); err != nil {
		t.Error("Unable to coerce to JSON", err)
	}

	if myJSONStruct.Headers.Host != "httpbin.org" {
		t.Error("The host header is invalid")
	}

	if resp.Bytes() != nil {
		t.Errorf("JSON decoding did not fully consume the response stream (Bytes) %#v", resp.Bytes())
	}

	if resp.String() != "" {
		t.Error("JSON decoding did not fully consume the response stream (String)", resp.String())
	}

	if resp.StatusCode != 200 {
		t.Error("Response returned a non-200 code")
	}

	return myJSONStruct
}
