package statuscake

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestTest_Validate(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	test := &Test{
		Timeout:      200,
		Confirmation: 100,
		Public:       200,
		Virus:        200,
		TestType:     "FTP",
		RealBrowser:  100,
		TriggerRate:  100,
		CheckRate:    100000,
		CustomHeader: "here be dragons",
		WebsiteName:  "",
		WebsiteURL:   "",
		DNSIP:        "127.0.0.1",
		DNSServer:    "1.1.1.1",
	}

	err := test.Validate()
	require.NotNil(err)

	message := err.Error()
	assert.Contains(message, "WebsiteName is required")
	assert.Contains(message, "WebsiteURL is required")
	assert.Contains(message, "Timeout must be 0 or between 6 and 99")
	assert.Contains(message, "Confirmation must be between 0 and 9")
	assert.Contains(message, "CheckRate must be between 0 and 23999")
	assert.Contains(message, "Public must be 0 or 1")
	assert.Contains(message, "Virus must be 0 or 1")
	assert.Contains(message, "TestType must be HTTP, TCP, DNS or PING")
	assert.Contains(message, "RealBrowser must be 0 or 1")
	assert.Contains(message, "TriggerRate must be between 0 and 59")
	assert.Contains(message, "CustomHeader must be provided as json string")
	assert.Contains(message, "DNSServer must be only used for DNS type tests")
	assert.Contains(message, "DNSIP must be only used for DNS type tests")

	test.Timeout = 10
	test.Confirmation = 2
	test.Public = 1
	test.Virus = 1
	test.TestType = "HTTP"
	test.RealBrowser = 1
	test.TriggerRate = 50
	test.CheckRate = 10
	test.WebsiteName = "Foo"
	test.WebsiteURL = "http://example.com"
	test.CustomHeader = `{"test": 15}`
	test.NodeLocations = []string{"foo", "bar"}
	test.DNSServer = ""
	test.DNSIP = ""

	err = test.Validate()
	assert.Nil(err)


	test2 := &Test{
		WebsiteURL: "example.com",
		WebsiteName: "Example dot com",
		TestType: "DNS",
		CheckRate: 300,
	}
	err2 := test2.Validate()
	require.NotNil(err2)

	message2 := err2.Error()
	assert.Contains(message2, "DNSIP is required")

	test2.DNSIP = "93.184.216.34"

	err2 = test.Validate()
	assert.Nil(err2)
}

func TestTest_ToURLValues(t *testing.T) {
	assert := assert.New(t)

	test := &Test{
		TestID:         123,
		Paused:         true,
		WebsiteName:    "Foo Bar",
		CustomHeader:   `{"some":{"json": ["value"]}}`,
		WebsiteURL:     "http://example.com",
		Port:           3000,
		NodeLocations:  []string{"foo", "bar"},
		Timeout:        11,
		PingURL:        "http://example.com/ping",
		Confirmation:   1,
		CheckRate:      500,
		BasicUser:      "myuser",
		BasicPass:      "mypass",
		Public:         1,
		LogoImage:      "http://example.com/logo.jpg",
		Branding:       1,
		WebsiteHost:    "hoster",
		Virus:          1,
		FindString:     "hello",
		DoNotFind:      true,
		TestType:       "HTTP",
		RealBrowser:    1,
		TriggerRate:    50,
		TestTags:       []string{"tag1", "tag2"},
		StatusCodes:    "500",
		EnableSSLAlert: false,
		FollowRedirect: false,
		DNSServer:      "127.0.0.1",
		DNSIP:          "1.1.1.1",
	}

	expected := url.Values{
		"TestID":         {"123"},
		"Paused":         {"1"},
		"WebsiteName":    {"Foo Bar"},
		"WebsiteURL":     {"http://example.com"},
		"CustomHeader":   {`{"some":{"json": ["value"]}}`},
		"Port":           {"3000"},
		"NodeLocations":  {"foo,bar"},
		"Timeout":        {"11"},
		"PingURL":        {"http://example.com/ping"},
		"ContactGroup":   {""},
		"Confirmation":   {"1"},
		"CheckRate":      {"500"},
		"BasicUser":      {"myuser"},
		"BasicPass":      {"mypass"},
		"Public":         {"1"},
		"LogoImage":      {"http://example.com/logo.jpg"},
		"Branding":       {"1"},
		"WebsiteHost":    {"hoster"},
		"Virus":          {"1"},
		"FindString":     {"hello"},
		"DoNotFind":      {"1"},
		"TestType":       {"HTTP"},
		"RealBrowser":    {"1"},
		"TriggerRate":    {"50"},
		"TestTags":       {"tag1,tag2"},
		"StatusCodes":    {"500"},
		"UseJar":         {"0"},
		"PostRaw":        {""},
		"PostBody":        {""},
		"FinalEndpoint":  {""},
		"EnableSSLAlert": {"0"},
		"FollowRedirect": {"0"},
		"DNSServer":      {"127.0.0.1"},
		"DNSIP":          {"1.1.1.1"},
	}

	assert.Equal(expected, test.ToURLValues())

	test.TestID = 0
	delete(expected, "TestID")

	assert.Equal(expected.Encode(), test.ToURLValues().Encode())

	test.StatusCodes = ""
	delete(expected, "StatusCodes")

	assert.Equal(expected.Encode(), test.ToURLValues().Encode())
}

func TestTests_All(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_all_ok.json",
	}
	tt := newTests(c)
	tests, err := tt.All()
	require.Nil(err)

	assert.Equal("/Tests", c.sentRequestPath)
	assert.Equal("GET", c.sentRequestMethod)
	assert.Nil(c.sentRequestValues)
	assert.Len(tests, 2)

	expectedTest := &Test{
		TestID:        100,
		Paused:        false,
		TestType:      "HTTP",
		WebsiteName:   "www 1",
		ContactGroup:  []string{"1"},
		Status:        "Up",
		Uptime:        100,
		NodeLocations: []string{"foo", "bar"},
	}
	assert.Equal(expectedTest, tests[0])

	expectedTest = &Test{
		TestID:        101,
		Paused:        true,
		TestType:      "HTTP",
		WebsiteName:   "www 2",
		ContactGroup:  []string{"2"},
		Status:        "Down",
		Uptime:        0,
		NodeLocations: []string{"foo"},
		TestTags:      []string{"test1", "test2"},
	}
	assert.Equal(expectedTest, tests[1])
}

func TestTests_AllWithFilter(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_all_ok.json",
	}

	v := url.Values{}
	v.Set("tags", "test1,test2")
	tt := newTests(c)
	tests, err := tt.AllWithFilter(v)
	require.Nil(err)

	assert.Equal("/Tests", c.sentRequestPath)
	assert.Equal("GET", c.sentRequestMethod)
	assert.NotNil(c.sentRequestValues)
	assert.Len(tests, 1)

	expectedTest := &Test{
		TestID:        101,
		Paused:        true,
		TestType:      "HTTP",
		WebsiteName:   "www 2",
		ContactGroup:  []string{"2"},
		Status:        "Down",
		Uptime:        0,
		NodeLocations: []string{"foo"},
		TestTags:      []string{"test1", "test2"},
	}
	assert.Equal(expectedTest, tests[0])
}

func TestTests_Update_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_update_ok.json",
	}
	tt := newTests(c)

	test1 := &Test{
		WebsiteName: "foo",
	}

	test2, err := tt.Update(test1)
	require.Nil(err)

	assert.Equal("/Tests/Update", c.sentRequestPath)
	assert.Equal("PUT", c.sentRequestMethod)
	assert.NotNil(c.sentRequestValues)
	assert.NotNil(test2)

	assert.Equal(1234, test2.TestID)
}

func TestTests_Update_Error(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_update_error.json",
	}
	tt := newTests(c)

	test1 := &Test{
		WebsiteName: "foo",
	}

	test2, err := tt.Update(test1)
	assert.Nil(test2)

	require.NotNil(err)
	assert.IsType(&updateError{}, err)
	assert.Contains(err.Error(), "issue a")
}

func TestTests_Update_ErrorWithSliceOfIssues(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_update_error_slice_of_issues.json",
	}
	tt := newTests(c)

	test1 := &Test{
		WebsiteName: "foo",
	}

	test2, err := tt.Update(test1)
	assert.Nil(test2)

	require.NotNil(err)
	assert.IsType(&updateError{}, err)
	assert.Equal("Required Data is Missing., hello, world", err.Error())
}

func TestTests_Delete_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_delete_ok.json",
	}
	tt := newTests(c)

	err := tt.Delete(1234)
	require.Nil(err)

	assert.Equal("/Tests/Details", c.sentRequestPath)
	assert.Equal("DELETE", c.sentRequestMethod)
	assert.Equal(url.Values{"TestID": {"1234"}}, c.sentRequestValues)
}

func TestTests_Delete_Error(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_delete_error.json",
	}
	tt := newTests(c)

	err := tt.Delete(1234)
	require.NotNil(err)
	assert.Equal("this is an error", err.Error())
}

func TestTests_Detail_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_detail_ok.json",
	}
	tt := newTests(c)

	test, err := tt.Detail(1234)
	require.Nil(err)

	assert.Equal("/Tests/Details", c.sentRequestPath)
	assert.Equal("GET", c.sentRequestMethod)
	assert.Equal(url.Values{"TestID": {"1234"}}, c.sentRequestValues)

	assert.Equal(test.TestID, 6735)
	assert.Equal(test.TestType, "HTTP")
	assert.Equal(test.Paused, false)
	assert.Equal(test.WebsiteName, "NL")
	assert.Equal(test.CustomHeader, `{"some":{"json": ["value"]}}`)
	assert.Equal(test.UserAgent, "product/version (comment)")
	assert.Equal(test.ContactGroup, []string{"536"})
	assert.Equal(test.Status, "Up")
	assert.Equal(test.Uptime, 0.0)
	assert.Equal(test.CheckRate, 60)
	assert.Equal(test.Timeout, 40)
	assert.Equal(test.LogoImage, "")
	assert.Equal(test.WebsiteHost, "Various")
	assert.Equal(test.FindString, "")
	assert.Equal(test.DoNotFind, false)
	assert.Equal(test.NodeLocations, []string{"foo", "bar"})
}

func TestTests_DNS_Detail_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_dns_detail_ok.json",
	}
	tt := newTests(c)

	test, err := tt.Detail(1337)
	require.Nil(err)

	assert.Equal("/Tests/Details", c.sentRequestPath)
	assert.Equal("GET", c.sentRequestMethod)
	assert.Equal(url.Values{"TestID": {"1337"}}, c.sentRequestValues)

	assert.Equal(test.TestID, 1337)
	assert.Equal(test.TestType, "DNS")
	assert.Equal(test.Paused, false)
	assert.Equal(test.WebsiteName, "A DNS test")
	assert.Equal(test.Status, "Up")
	assert.Equal(test.Uptime, 100.0)
	assert.Equal(test.CheckRate, 300)
	assert.Equal(test.DNSServer, "1.1.1.1")
	assert.Equal(test.DNSIP, "93.184.216.34")
}

func TestTests_DNS_Create_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	c := &fakeAPIClient{
		fixture: "tests_dns_create_ok.json",
	}
	tt := newTests(c)

	test1 := &Test{
		WebsiteName: "Example dot com",
		WebsiteURL:  "http://example.com",
		CheckRate:   300,
		DNSServer:   "1.1.1.1",
		DNSIP:       "93.184.216.34",
	}

	test2, err := tt.Update(test1)
	require.Nil(err)

	assert.Equal("/Tests/Update", c.sentRequestPath)
	assert.Equal("PUT", c.sentRequestMethod)
	assert.Equal(test1.WebsiteName, test2.WebsiteName)
	assert.Equal(test1.WebsiteURL, test2.WebsiteURL)
	assert.Equal(test1.DNSIP, test2.DNSIP)
	assert.Equal(test1.DNSServer, test2.DNSServer)
	assert.NotNil(c.sentRequestValues)
	assert.NotNil(test2)

	assert.Equal(1337, test2.TestID)
}

type fakeAPIClient struct {
	sentRequestPath   string
	sentRequestMethod string
	sentRequestValues url.Values
	fixture           string
}

func (c *fakeAPIClient) put(path string, v url.Values) (*http.Response, error) {
	return c.all("PUT", path, v)
}

func (c *fakeAPIClient) delete(path string, v url.Values) (*http.Response, error) {
	return c.all("DELETE", path, v)
}

func (c *fakeAPIClient) get(path string, v url.Values) (*http.Response, error) {
	return c.all("GET", path, v)
}

func (c *fakeAPIClient) all(method string, path string, v url.Values) (*http.Response, error) {
	c.sentRequestMethod = method
	c.sentRequestPath = path
	c.sentRequestValues = v

	p := filepath.Join("fixtures", c.fixture)
	f, err := os.Open(p)
	if err != nil {
		log.Fatal(err)
	}

	var resp *http.Response
	if len(c.sentRequestValues.Get("tags")) > 0 {
		var storedResponses []Test
		var returnResponses []Test
		byteValue, _ := ioutil.ReadAll(f)
		json.Unmarshal(byteValue, &storedResponses)
		requestedTags := strings.Split(c.sentRequestValues.Get("tags"), ",")

		for _, storedResponse := range storedResponses {
			if len(requestedTags) > len(storedResponse.TestTags) { // if we are requesting more tags than what's stored then there are no matches
				continue
			}

			match := true
			for i, tag := range requestedTags {
				if tag != storedResponse.TestTags[i] {
					match = false
				}
			}

			if match { // we can add it to the response now
				returnResponses = append(returnResponses, storedResponse)
			}
		}

		if len(returnResponses) == 0 {
			return nil, nil
		}

		newByteValue, _ := json.Marshal(&returnResponses)
		resp = &http.Response{
			Body: ioutil.NopCloser(bytes.NewBuffer(newByteValue)),
		}

	} else {
		resp = &http.Response{
			Body: f,
		}
	}

	return resp, nil
}
