// Copyright 2015 CoreOS, Inc.
//
// 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.

package pkg

import (
	"fmt"
	"io"
	"math"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

func TestExpBackoff(t *testing.T) {
	duration := time.Millisecond
	max := time.Hour
	for i := 0; i < math.MaxUint16; i++ {
		duration = ExpBackoff(duration, max)
		if duration < 0 {
			t.Fatalf("duration too small: %v %v", duration, i)
		}
		if duration > max {
			t.Fatalf("duration too large: %v %v", duration, i)
		}
	}
}

// Test exponential backoff and that it continues retrying if a 5xx response is
// received
func TestGetURLExpBackOff(t *testing.T) {
	var expBackoffTests = []struct {
		count int
		body  string
	}{
		{0, "number of attempts: 0"},
		{1, "number of attempts: 1"},
		{2, "number of attempts: 2"},
	}
	client := NewHTTPClient()

	for i, tt := range expBackoffTests {
		mux := http.NewServeMux()
		count := 0
		mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
			if count == tt.count {
				io.WriteString(w, fmt.Sprintf("number of attempts: %d", count))
				return
			}
			count++
			http.Error(w, "", 500)
		})
		ts := httptest.NewServer(mux)
		defer ts.Close()

		data, err := client.GetRetry(ts.URL)
		if err != nil {
			t.Errorf("Test case %d produced error: %v", i, err)
		}

		if count != tt.count {
			t.Errorf("Test case %d failed: %d != %d", i, count, tt.count)
		}

		if string(data) != tt.body {
			t.Errorf("Test case %d failed: %s != %s", i, tt.body, data)
		}
	}
}

// Test that it stops retrying if a 4xx response comes back
func TestGetURL4xx(t *testing.T) {
	client := NewHTTPClient()
	retries := 0
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		retries++
		http.Error(w, "", 404)
	}))
	defer ts.Close()

	_, err := client.GetRetry(ts.URL)
	if err == nil {
		t.Errorf("Incorrect result\ngot:  %s\nwant: %s", err.Error(), "Not found. HTTP status code: 404")
	}

	if retries > 1 {
		t.Errorf("Number of retries:\n%d\nExpected number of retries:\n%d", retries, 1)
	}
}

// Test that it fetches and returns user-data just fine
func TestGetURL2xx(t *testing.T) {
	var cloudcfg = `
#cloud-config
coreos: 
	oem:
	    id: test
	    name: CoreOS.box for Test
	    version-id: %VERSION_ID%+%BUILD_ID%
	    home-url: https://github.com/rancher/os/config/cloudinit
	    bug-report-url: https://github.com/rancher/os/config/cloudinit
	update:
		reboot-strategy: best-effort
`

	client := NewHTTPClient()
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, cloudcfg)
	}))
	defer ts.Close()

	data, err := client.GetRetry(ts.URL)
	if err != nil {
		t.Errorf("Incorrect result\ngot:  %v\nwant: %v", err, nil)
	}

	if string(data) != cloudcfg {
		t.Errorf("Incorrect result\ngot:  %s\nwant: %s", string(data), cloudcfg)
	}
}

// Test attempt to fetching using malformed URL
func TestGetMalformedURL(t *testing.T) {
	client := NewHTTPClient()

	var tests = []struct {
		url  string
		want string
	}{
		{"boo", "URL boo does not have a valid HTTP scheme. Skipping"},
		{"mailto://boo", "URL mailto://boo does not have a valid HTTP scheme. Skipping"},
		{"ftp://boo", "URL ftp://boo does not have a valid HTTP scheme. Skipping"},
		{"", "URL is empty. Skipping"},
	}

	for _, test := range tests {
		_, err := client.GetRetry(test.url)
		if err == nil || err.Error() != test.want {
			t.Errorf("Incorrect result\ngot:  %v\nwant: %v", err, test.want)
		}
	}
}
