package digitalocean

import (
	"errors"
	"net"
	"testing"

	"github.com/digitalocean/godo"

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

func TestProvider_New(t *testing.T) {
	assert.Equal(t, Provider{
		service: godo.NewFromToken("token").Domains,
		cache:   make(map[string][]godo.DomainRecord),
	}, New("token"))
}

func TestProvider_fetch(t *testing.T) {
	expected := []godo.DomainRecord{
		{
			ID:       28448429,
			Type:     "A",
			Name:     "sub1",
			Data:     "201.110.66.72",
			Priority: 2,
			TTL:      1800,
		},
		{
			ID:       28448430,
			Type:     "A",
			Name:     "sub2",
			Data:     "242.124.218.187",
			Priority: 1,
			TTL:      1800,
		},
	}

	provider := Provider{
		service: mock{
			t: t,
			records_by_type: map[string][]godo.DomainRecord{
				"example.com": expected,
			},
		},
		cache: make(map[string][]godo.DomainRecord),
	}

	records, err := provider.fetch("example.com")
	assert.NoError(t, err)

	assert.Equal(t, expected, records)

	// Fetch invalid
	_, err = provider.fetch("noexists.com")
	assert.Error(t, err)
}

func TestProvider_fetch_caches_records(t *testing.T) {
	example_com_records := []godo.DomainRecord{
		{
			ID:       28448429,
			Type:     "A",
			Name:     "sub1",
			Data:     "107.218.197.189",
			Priority: 2,
			TTL:      1800,
		},
		{
			ID:       28448430,
			Type:     "A",
			Name:     "sub2",
			Data:     "254.221.12.160",
			Priority: 1,
			TTL:      1800,
		},
	}

	another_com_records := []godo.DomainRecord{
		{
			ID:       237823,
			Type:     "A",
			Name:     "box",
			Data:     "108.151.98.62",
			Priority: 2,
			TTL:      1800,
		},
		{
			ID:       237824,
			Type:     "A",
			Name:     "ntp",
			Data:     "190.255.140.208",
			Priority: 10,
			TTL:      300,
		},
	}

	mockService := mock{
		t: t,
		records_by_type: map[string][]godo.DomainRecord{
			"example.com": example_com_records,
			"another.com": another_com_records,
		},
	}

	provider := Provider{
		service: mockService,
		cache:   make(map[string][]godo.DomainRecord),
	}

	records, err := provider.fetch("example.com")
	assert.NoError(t, err)
	assert.Equal(t, example_com_records, records)

	records, err = provider.fetch("another.com")
	assert.NoError(t, err)
	assert.Equal(t, another_com_records, records)

	// Check cache, should be equal to the map in mock service.
	assert.Equal(t, provider.cache, mockService.records_by_type)
}

func TestProvider_fetch_from_cache(t *testing.T) {
	expected := []godo.DomainRecord{
		{
			ID:       273671823,
			Type:     "A",
			Name:     "sub1",
			Data:     "42.170.152.94",
			Priority: 10,
			TTL:      1800,
		},
	}

	provider := Provider{
		service: mock{t: t},
		cache: map[string][]godo.DomainRecord{
			"example.com": expected,
		},
	}

	records, err := provider.fetch("example.com")
	assert.NoError(t, err)

	assert.Equal(t, expected, records)

	_, err = provider.fetch("noexists.com")
	assert.Error(t, err)
}

func TestProvider_find(t *testing.T) {
	expected := []godo.DomainRecord{
		{
			ID:       236718,
			Type:     "A",
			Name:     "sub1",
			Data:     "161.125.137.64",
			Priority: 10,
			TTL:      1800,
		},
		{
			ID:       23123131,
			Type:     "A",
			Name:     "sub2",
			Data:     "154.63.46.159",
			Priority: 5,
			TTL:      1800,
		},
	}

	expected_cache := []godo.DomainRecord{
		{
			ID:       23713762,
			Type:     "A",
			Name:     "mail",
			Data:     "176.151.152.10",
			Priority: 10,
			TTL:      3600,
		},
	}

	provider := Provider{
		service: mock{
			t: t,
			records_by_type: map[string][]godo.DomainRecord{
				"example.com": expected,
			},
		},
		cache: map[string][]godo.DomainRecord{
			"cached.com": expected_cache,
		},
	}

	// Test fetch.
	record, err := provider.find("example.com", "sub2")
	assert.NoError(t, err)
	assert.Equal(t, expected[1], *record)

	// Test cached record
	record, err = provider.find("cached.com", "mail")
	assert.NoError(t, err)
	assert.Equal(t, expected_cache[0], *record)

	// Test not found (domain)
	_, err = provider.find("noexists.com", "www")
	assert.Error(t, err)

	// Test not found (subdomain)
	_, err = provider.find("cached.com", "nosub")
	assert.Error(t, err)
}

func TestProvider_Update(t *testing.T) {
	expected := []godo.DomainRecord{
		{
			ID:       1337,
			Type:     "A",
			Name:     "www",
			Data:     "80.17.42.157",
			Priority: 10,
			TTL:      360,
			Port:     22,
			Weight:   100,
			Flags:    0xf1,
			Tag:      "some_tag",
		},
	}

	mockService := mock{
		t: t,
		records_by_type: map[string][]godo.DomainRecord{
			"example.com": expected,
		},
		edit_record_request: &godo.DomainRecordEditRequest{
			// Type:     "A",
			// Name:     "www",
			Data: "221.135.170.186",
			// Priority: 10,
			// Port:     22,
			// TTL:      360,
			// Weight:   100,
			// Flags:    0xf1,
			// Tag:      "some_tag",
		},
	}

	provider := Provider{
		service: &mockService,
		cache:   map[string][]godo.DomainRecord{},
	}

	err := provider.Update("example.com", "www", net.IPv4(221, 135, 170, 186))
	assert.NoError(t, err)

	err = provider.Update("invalid.com", "www", net.IPv4(72, 82, 118, 186))
	assert.Error(t, err)

	mockService.edit_record_error = errors.New("Error")

	err = provider.Update("example.com", "www", net.IPv4(221, 135, 170, 186))
	assert.Error(t, err)
}
