package internal

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

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

func handlerMock(method string, jsonData []byte) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != method {
			http.Error(rw, "Incorrect method used", http.StatusBadRequest)
			return
		}

		_, err := rw.Write(jsonData)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
	})
}

func TestClientGetZone(t *testing.T) {
	type result struct {
		zone  *Zone
		error bool
	}
	testCases := []struct {
		desc        string
		authFQDN    string
		apiResponse []byte
		expected    result
	}{
		{
			desc:        "zone found",
			authFQDN:    "_acme-challenge.foo.com.",
			apiResponse: []byte(`{"name": "foo.com", "type": "master", "zone": "zone", "status": "1"}`),
			expected: result{
				zone: &Zone{
					Name:   "foo.com",
					Type:   "master",
					Zone:   "zone",
					Status: "1",
				},
			},
		},
		{
			desc:        "zone not found",
			authFQDN:    "_acme-challenge.foo.com.",
			apiResponse: []byte(``),
			expected:    result{error: true},
		},
	}

	for _, test := range testCases {
		t.Run(test.desc, func(t *testing.T) {
			server := httptest.NewServer(handlerMock(http.MethodGet, test.apiResponse))

			client, _ := NewClient("myAuthID", "", "myAuthPassword")
			mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL))
			client.BaseURL = mockBaseURL

			zone, err := client.GetZone(test.authFQDN)

			if test.expected.error {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, test.expected.zone, zone)
			}
		})
	}
}

func TestClientFindTxtRecord(t *testing.T) {
	type result struct {
		txtRecord *TXTRecord
		error     bool
	}

	testCases := []struct {
		desc        string
		authFQDN    string
		zoneName    string
		apiResponse []byte
		expected    result
	}{
		{
			desc:     "record found",
			authFQDN: "_acme-challenge.foo.com.",
			zoneName: "foo.com",
			apiResponse: []byte(`{
  "5769228": {
    "id": "5769228",
    "type": "TXT",
    "host": "_acme-challenge",
    "record": "txtTXTtxtTXTtxtTXTtxtTXT",
    "failover": "0",
    "ttl": "3600",
    "status": 1
  },
  "181805209": {
    "id": "181805209",
    "type": "TXT",
    "host": "_github-challenge",
    "record": "b66b8324b5",
    "failover": "0",
    "ttl": "300",
    "status": 1
  }
}`),
			expected: result{
				txtRecord: &TXTRecord{
					ID:       5769228,
					Type:     "TXT",
					Host:     "_acme-challenge",
					Record:   "txtTXTtxtTXTtxtTXTtxtTXT",
					Failover: 0,
					TTL:      3600,
					Status:   1,
				},
			},
		},
		{
			desc:        "record not found",
			authFQDN:    "_acme-challenge.foo.com.",
			zoneName:    "test-zone",
			apiResponse: []byte(`[]`),
			expected:    result{txtRecord: nil},
		},
	}

	for _, test := range testCases {
		t.Run(test.desc, func(t *testing.T) {
			server := httptest.NewServer(handlerMock(http.MethodGet, test.apiResponse))

			client, err := NewClient("myAuthID", "", "myAuthPassword")
			require.NoError(t, err)

			mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL))
			client.BaseURL = mockBaseURL

			txtRecord, err := client.FindTxtRecord(test.zoneName, test.authFQDN)

			if test.expected.error {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, test.expected.txtRecord, txtRecord)
			}
		})
	}
}

func TestClientAddTxtRecord(t *testing.T) {
	type expected struct {
		Query string
		Error string
	}

	testCases := []struct {
		desc        string
		authID      string
		subAuthID   string
		zone        *Zone
		authFQDN    string
		value       string
		ttl         int
		apiResponse []byte
		expected    expected
	}{
		{
			desc:   "sub-zone",
			authID: "myAuthID",
			zone: &Zone{
				Name:   "bar.com",
				Type:   "master",
				Zone:   "domain",
				Status: "1",
			},
			authFQDN:    "_acme-challenge.foo.bar.com.",
			value:       "txtTXTtxtTXTtxtTXTtxtTXT",
			ttl:         60,
			apiResponse: []byte(`{"status":"Success","statusDescription":"The record was added successfully."}`),
			expected: expected{
				Query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=bar.com&host=_acme-challenge.foo&record=txtTXTtxtTXTtxtTXTtxtTXT&record-type=TXT&ttl=60`,
			},
		},
		{
			desc:   "main zone (authID)",
			authID: "myAuthID",
			zone: &Zone{
				Name:   "bar.com",
				Type:   "master",
				Zone:   "domain",
				Status: "1",
			},
			authFQDN:    "_acme-challenge.bar.com.",
			value:       "TXTtxtTXTtxtTXTtxtTXTtxt",
			ttl:         60,
			apiResponse: []byte(`{"status":"Success","statusDescription":"The record was added successfully."}`),
			expected: expected{
				Query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=bar.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=60`,
			},
		},
		{
			desc:      "main zone (subAuthID)",
			authID:    "myAuthID",
			subAuthID: "mySubAuthID",
			zone: &Zone{
				Name:   "bar.com",
				Type:   "master",
				Zone:   "domain",
				Status: "1",
			},
			authFQDN:    "_acme-challenge.bar.com.",
			value:       "TXTtxtTXTtxtTXTtxtTXTtxt",
			ttl:         60,
			apiResponse: []byte(`{"status":"Success","statusDescription":"The record was added successfully."}`),
			expected: expected{
				Query: `auth-password=myAuthPassword&domain-name=bar.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&sub-auth-id=mySubAuthID&ttl=60`,
			},
		},
		{
			desc:   "invalid status",
			authID: "myAuthID",
			zone: &Zone{
				Name:   "bar.com",
				Type:   "master",
				Zone:   "domain",
				Status: "1",
			},
			authFQDN:    "_acme-challenge.bar.com.",
			value:       "TXTtxtTXTtxtTXTtxtTXTtxt",
			ttl:         120,
			apiResponse: []byte(`{"status":"Failed","statusDescription":"Invalid TTL. Choose from the list of the values we support."}`),
			expected: expected{
				Query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=bar.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=300`,
				Error: "fail to add TXT record: Failed Invalid TTL. Choose from the list of the values we support.",
			},
		},
	}

	for _, test := range testCases {
		t.Run(test.desc, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
				assert.NotNil(t, req.URL.RawQuery)
				assert.Equal(t, test.expected.Query, req.URL.RawQuery)

				handlerMock(http.MethodPost, test.apiResponse).ServeHTTP(rw, req)
			}))

			client, err := NewClient(test.authID, test.subAuthID, "myAuthPassword")
			require.NoError(t, err)

			mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL))
			client.BaseURL = mockBaseURL

			err = client.AddTxtRecord(test.zone.Name, test.authFQDN, test.value, test.ttl)

			if test.expected.Error != "" {
				require.EqualError(t, err, test.expected.Error)
			} else {
				require.NoError(t, err)
			}
		})
	}
}
