package internal

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"strconv"
	"testing"

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

const testBaseURL = "https://testapi.internet.bs"

const (
	testAPIKey   = "testapi"
	testPassword = "testpass"
)

func TestClient_AddRecord(t *testing.T) {
	client := setupTest(t, "/Domain/DnsRecord/Add", "./fixtures/Domain_DnsRecord_Add_SUCCESS.json")

	query := RecordQuery{
		FullRecordName: "www.example.com",
		Type:           "TXT",
		Value:          "xxx",
		TTL:            36000,
	}

	err := client.AddRecord(context.Background(), query)
	require.NoError(t, err)
}

func TestClient_AddRecord_error(t *testing.T) {
	client := setupTest(t, "/Domain/DnsRecord/Add", "./fixtures/Domain_DnsRecord_Add_FAILURE.json")

	query := RecordQuery{
		FullRecordName: "www.example.com.",
		Type:           "TXT",
		Value:          "xxx",
		TTL:            36000,
	}

	err := client.AddRecord(context.Background(), query)
	require.Error(t, err)
}

func TestClient_AddRecord_integration(t *testing.T) {
	env, ok := os.LookupEnv("INTERNET_BS_DEBUG")
	if !ok {
		t.Skip("skip integration test")
	}

	client := NewClient(testAPIKey, testPassword)
	client.baseURL, _ = url.Parse(testBaseURL)
	client.debug, _ = strconv.ParseBool(env)

	query := RecordQuery{
		FullRecordName: "www.example.com",
		Type:           "TXT",
		Value:          "xxx",
		TTL:            36000,
	}

	err := client.AddRecord(context.Background(), query)
	require.NoError(t, err)

	query = RecordQuery{
		FullRecordName: "www.example.com",
		Type:           "TXT",
		Value:          "yyy",
		TTL:            36000,
	}

	err = client.AddRecord(context.Background(), query)
	require.NoError(t, err)
}

func TestClient_RemoveRecord(t *testing.T) {
	client := setupTest(t, "/Domain/DnsRecord/Remove", "./fixtures/Domain_DnsRecord_Remove_SUCCESS.json")

	query := RecordQuery{
		FullRecordName: "www.example.com",
		Type:           "TXT",
		Value:          "",
	}
	err := client.RemoveRecord(context.Background(), query)
	require.NoError(t, err)
}

func TestClient_RemoveRecord_error(t *testing.T) {
	client := setupTest(t, "/Domain/DnsRecord/Remove", "./fixtures/Domain_DnsRecord_Remove_FAILURE.json")

	query := RecordQuery{
		FullRecordName: "www.example.com.",
		Type:           "TXT",
		Value:          "",
	}
	err := client.RemoveRecord(context.Background(), query)
	require.Error(t, err)
}

func TestClient_RemoveRecord_integration(t *testing.T) {
	env, ok := os.LookupEnv("INTERNET_BS_DEBUG")
	if !ok {
		t.Skip("skip integration test")
	}

	client := NewClient(testAPIKey, testPassword)
	client.baseURL, _ = url.Parse(testBaseURL)
	client.debug, _ = strconv.ParseBool(env)

	query := RecordQuery{
		FullRecordName: "www.example.com",
		Type:           "TXT",
		Value:          "",
	}

	err := client.RemoveRecord(context.Background(), query)
	require.NoError(t, err)
}

func TestClient_ListRecords(t *testing.T) {
	client := setupTest(t, "/Domain/DnsRecord/List", "./fixtures/Domain_DnsRecord_List_SUCCESS.json")

	query := ListRecordQuery{
		Domain: "example.com",
	}

	records, err := client.ListRecords(context.Background(), query)
	require.NoError(t, err)

	expected := []Record{
		{
			Name:  "example.com",
			Value: "ns-hongkong.internet.bs",
			TTL:   3600,
			Type:  "NS",
		},
		{
			Name:  "example.com",
			Value: "ns-toronto.internet.bs",
			TTL:   3600,
			Type:  "NS",
		},
		{
			Name:  "example.com",
			Value: "ns-london.internet.bs",
			TTL:   3600,
			Type:  "NS",
		},
		{
			Name:  "test.example.com",
			Value: "example1.com",
			TTL:   3600,
			Type:  "CNAME",
		},
		{
			Name:  "www.example.com",
			Value: "xxx",
			TTL:   36000,
			Type:  "TXT",
		},
		{
			Name:  "www.example.com",
			Value: "yyy",
			TTL:   36000,
			Type:  "TXT",
		},
	}

	assert.Equal(t, expected, records)
}

func TestClient_ListRecords_error(t *testing.T) {
	client := setupTest(t, "/Domain/DnsRecord/List", "./fixtures/Domain_DnsRecord_List_FAILURE.json")

	query := ListRecordQuery{
		Domain: "www.example.com",
	}

	_, err := client.ListRecords(context.Background(), query)
	require.Error(t, err)
}

func TestClient_ListRecords_integration(t *testing.T) {
	env, ok := os.LookupEnv("INTERNET_BS_DEBUG")
	if !ok {
		t.Skip("skip integration test")
	}

	client := NewClient(testAPIKey, testPassword)
	client.baseURL, _ = url.Parse(testBaseURL)
	client.debug, _ = strconv.ParseBool(env)

	query := ListRecordQuery{
		Domain: "example.com",
	}

	records, err := client.ListRecords(context.Background(), query)
	require.NoError(t, err)

	for _, record := range records {
		fmt.Println(record)
	}
}

func setupTest(t *testing.T, path, filename string) *Client {
	t.Helper()

	mux := http.NewServeMux()
	server := httptest.NewServer(mux)
	t.Cleanup(server.Close)

	mux.HandleFunc(path, testHandler(filename))

	client := NewClient(testAPIKey, testPassword)
	client.baseURL, _ = url.Parse(server.URL)

	return client
}

func testHandler(filename string) http.HandlerFunc {
	return func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed)
			return
		}

		if req.FormValue("apiKey") != testAPIKey {
			http.Error(rw, `{"transactid":"d46d812569acdb8b39c3933ec4351e79","status":"FAILURE","message":"Invalid API key and\/or Password","code":107002}`, http.StatusOK)
			return
		}

		if req.FormValue("password") != testPassword {
			http.Error(rw, `{"transactid":"d46d812569acdb8b39c3933ec4351e79","status":"FAILURE","message":"Invalid API key and\/or Password","code":107002}`, http.StatusOK)
			return
		}

		file, err := os.Open(filename)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}

		defer func() { _ = file.Close() }()

		_, err = io.Copy(rw, file)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}
