package internal

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"testing"

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

func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client {
	t.Helper()

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

	client := NewClient("secret")
	client.HTTPClient = server.Client()
	client.baseURL, _ = url.Parse(server.URL)

	mux.HandleFunc(pattern, handler)

	return client
}

func writeFixture(rw http.ResponseWriter, filename string) {
	file, err := os.Open(filepath.Join("fixtures", filename))
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}
	defer func() { _ = file.Close() }()

	_, _ = io.Copy(rw, file)
}

func TestClient_ListZoneConfigs(t *testing.T) {
	client := setupTest(t, "/zoneConfigsFind", func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest)
			return
		}

		raw, err := io.ReadAll(req.Body)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}

		body := string(bytes.TrimSpace(raw))
		if body != `{"authToken":"secret","filter":{"field":"zoneName","value":"example.com"},"limit":1,"page":1}` {
			http.Error(rw, fmt.Sprintf("unexpected body: got %s", body), http.StatusBadRequest)
			return
		}

		writeFixture(rw, "zoneConfigsFind.json")
	})

	zonesFind := ZoneConfigsFindRequest{
		Filter: Filter{Field: "zoneName", Value: "example.com"},
		Limit:  1,
		Page:   1,
	}

	zoneResponse, err := client.ListZoneConfigs(context.Background(), zonesFind)
	require.NoError(t, err)

	expected := &ZoneResponse{
		Limit:        10,
		Page:         1,
		TotalEntries: 15,
		TotalPages:   2,
		Type:         "FindZoneConfigsResult",
		Data: []ZoneConfig{{
			ID:                    "123",
			AccountID:             "456",
			Status:                "s",
			Name:                  "n",
			NameUnicode:           "u",
			MasterIP:              "m",
			Type:                  "t",
			EMailAddress:          "e",
			ZoneTransferWhitelist: []string{"a", "b"},
			LastChangeDate:        "l",
			DNSServerGroupID:      "g",
			DNSSecMode:            "m",
			SOAValues: &SOAValues{
				Refresh:     1,
				Retry:       2,
				Expire:      3,
				TTL:         4,
				NegativeTTL: 5,
			},
			TemplateValues: json.RawMessage(nil),
		}},
	}

	assert.Equal(t, expected, zoneResponse)
}

func TestClient_ListZoneConfigs_error(t *testing.T) {
	client := setupTest(t, "/zoneConfigsFind", func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest)
			return
		}

		writeFixture(rw, "zoneConfigsFind_error.json")
	})

	zonesFind := ZoneConfigsFindRequest{
		Filter: Filter{Field: "zoneName", Value: "example.com"},
		Limit:  1,
		Page:   1,
	}

	_, err := client.ListZoneConfigs(context.Background(), zonesFind)
	require.Error(t, err)
}

func TestClient_UpdateZone(t *testing.T) {
	client := setupTest(t, "/zoneUpdate", func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest)
			return
		}

		raw, err := io.ReadAll(req.Body)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}

		body := string(bytes.TrimSpace(raw))
		if body != `{"authToken":"secret","zoneConfig":{"id":"123","accountId":"456","status":"s","name":"n","nameUnicode":"u","masterIp":"m","type":"t","emailAddress":"e","zoneTransferWhitelist":["a","b"],"lastChangeDate":"l","dnsServerGroupId":"g","dnsSecMode":"m","soaValues":{"refresh":1,"retry":2,"expire":3,"ttl":4,"negativeTtl":5}},"recordsToAdd":null,"recordsToDelete":[{"name":"_acme-challenge.example.com","type":"TXT","content":"\"txt\""}]}` {
			http.Error(rw, fmt.Sprintf("unexpected body: got %s", body), http.StatusBadRequest)
			return
		}

		writeFixture(rw, "zoneUpdate.json")
	})

	request := ZoneUpdateRequest{
		ZoneConfig: ZoneConfig{
			ID:                    "123",
			AccountID:             "456",
			Status:                "s",
			Name:                  "n",
			NameUnicode:           "u",
			MasterIP:              "m",
			Type:                  "t",
			EMailAddress:          "e",
			ZoneTransferWhitelist: []string{"a", "b"},
			LastChangeDate:        "l",
			DNSServerGroupID:      "g",
			DNSSecMode:            "m",
			SOAValues: &SOAValues{
				Refresh:     1,
				Retry:       2,
				Expire:      3,
				TTL:         4,
				NegativeTTL: 5,
			},
		},
		RecordsToDelete: []DNSRecord{{
			Type:    "TXT",
			Name:    "_acme-challenge.example.com",
			Content: `"txt"`,
		}},
	}

	response, err := client.UpdateZone(context.Background(), request)
	require.NoError(t, err)

	expected := &Zone{
		Records: []DNSRecord{{
			ID:               "123",
			ZoneID:           "456",
			RecordTemplateID: "789",
			Name:             "n",
			Type:             "TXT",
			Content:          "txt",
			TTL:              120,
			Priority:         5,
			LastChangeDate:   "d",
		}},
		ZoneConfig: ZoneConfig{
			ID:                    "123",
			AccountID:             "456",
			Status:                "s",
			Name:                  "n",
			NameUnicode:           "u",
			MasterIP:              "m",
			Type:                  "t",
			EMailAddress:          "e",
			ZoneTransferWhitelist: []string{"a", "b"},
			LastChangeDate:        "l",
			DNSServerGroupID:      "g",
			DNSSecMode:            "m",
			SOAValues: &SOAValues{
				Refresh:     1,
				Retry:       2,
				Expire:      3,
				TTL:         4,
				NegativeTTL: 5,
			},
		},
	}

	assert.Equal(t, expected, response)
}

func TestClient_UpdateZone_error(t *testing.T) {
	client := setupTest(t, "/zoneUpdate", func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest)
			return
		}

		writeFixture(rw, "zoneUpdate_error.json")
	})

	request := ZoneUpdateRequest{
		ZoneConfig: ZoneConfig{
			ID:                    "123",
			AccountID:             "456",
			Status:                "s",
			Name:                  "n",
			NameUnicode:           "u",
			MasterIP:              "m",
			Type:                  "t",
			EMailAddress:          "e",
			ZoneTransferWhitelist: []string{"a", "b"},
			LastChangeDate:        "l",
			DNSServerGroupID:      "g",
			DNSSecMode:            "m",
			SOAValues: &SOAValues{
				Refresh:     1,
				Retry:       2,
				Expire:      3,
				TTL:         4,
				NegativeTTL: 5,
			},
		},
		RecordsToDelete: []DNSRecord{{
			Type:    "TXT",
			Name:    "_acme-challenge.example.com",
			Content: `"txt"`,
		}},
	}

	_, err := client.UpdateZone(context.Background(), request)
	require.Error(t, err)
}
