package dnscache

import (
	"context"
	"net"
	"strings"
	"testing"
	"time"

	"github.com/TykTechnologies/tyk/config"
)

func TestWrapDialerDialContextFunc(t *testing.T) {
	tearDownTestStorageFetchItem := setupTestStorageFetchItem(&configTestStorageFetchItem{t, etcHostsMap, etcHostsErrorMap})
	defer tearDownTestStorageFetchItem()

	expectedHost := "orig-host.com"
	expectedSingleIpHost := "single.orig-host.com"
	hostWithPort := expectedHost + ":8078"
	singleIpHostWithPort := expectedSingleIpHost + ":8078"
	dialerContext, cancel := context.WithCancel(context.TODO())
	cancel() //Manually disable connection establishment

	cases := []struct {
		name string

		address          string
		multiIPsStrategy config.IPsHandleStrategy
		initStorage      bool

		shouldCallFetchItem bool
		shouldCallDelete    bool
		expectedHostname    string
		expectedError       string
	}{
		{
			"PickFirstStrategy(1 ip): Should parse address, call storage.FetchItem, cache ip, call storage.Delete on DialContext error",
			singleIpHostWithPort, config.PickFirstStrategy, true,
			true, true, expectedSingleIpHost, "operation was canceled",
		},
		{
			"PickFirstStrategy(>1 ip): Should parse address, call storage.FetchItem, cache all ips, call storage.Delete on DialContext error",
			hostWithPort, config.PickFirstStrategy, true,
			true, true, expectedHost, "operation was canceled",
		},
		{
			"NoCacheStrategy(1 ip): Should parse address, call storage.FetchItem, cache ip, call storage.Delete on DialContext error",
			singleIpHostWithPort, config.NoCacheStrategy, true,
			true, true, expectedSingleIpHost, "operation was canceled",
		},
		{
			"NoCacheStrategy(>1 ip): Should parse address, call storage.FetchItem, remove ips caching",
			hostWithPort, config.NoCacheStrategy, true,
			true, true, expectedHost, "operation was canceled",
		},
		{
			"RandomStrategy(>1 ip): Should parse address, call storage.FetchItem, cache all ips, connect to random ip, call storage.Delete on DialContext error",
			hostWithPort, config.RandomStrategy, true,
			true, true, expectedHost, "operation was canceled",
		},
		{
			"Shouldn't call FetchItem when caching is disabled(storage == nil)",
			hostWithPort, config.NoCacheStrategy, false,
			false, false, "", "",
		},
		{
			"Shouldn't cache ipv4 address",
			"192.0.2.10:80", config.NoCacheStrategy, true,
			false, false, "", "operation was canceled",
		},
		{
			"Should faifast on address without port(accept only address with port)",
			expectedHost, config.NoCacheStrategy, true,
			false, false, "", "missing port in address",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			var fetchItemCall, deleteCall struct {
				called bool
				key    string
			}

			storage := &MockStorage{func(key string) ([]string, error) {
				fetchItemCall.called = true
				fetchItemCall.key = key
				return etcHostsMap[key+"."], nil
			}, func(key string) (DnsCacheItem, bool) {
				if _, ok := etcHostsMap[key]; ok {
					return DnsCacheItem{}, true
				}

				return DnsCacheItem{}, false
			}, func(key string, addrs []string) {},
				func(key string) {
					deleteCall.called = true
					deleteCall.key = key
				}, func() {}}

			dnsManager := NewDnsCacheManager(tc.multiIPsStrategy)
			if tc.initStorage {
				dnsManager.SetCacheStorage(storage)
			}

			_, err := dnsManager.WrapDialer(&net.Dialer{
				Timeout:   1 * time.Second,
				KeepAlive: 0,
			})(dialerContext, "tcp", tc.address)

			if tc.expectedError != "" {
				if err != nil && !strings.Contains(err.Error(), tc.expectedError) {
					t.Fatalf("wanted error '%s', got '%s'", tc.expectedError, err.Error())
				}
			}

			if tc.shouldCallFetchItem != fetchItemCall.called {
				t.Fatalf("wanted fetchItemCall.called to be %v, got %v", tc.shouldCallFetchItem, fetchItemCall.called)
			}

			if tc.shouldCallFetchItem {
				if fetchItemCall.key != tc.expectedHostname {
					t.Fatalf("wanted fetchItemCall.key to be %v, got %v", tc.expectedHostname, fetchItemCall.key)
				}
			}

			if tc.shouldCallDelete != deleteCall.called {
				t.Fatalf("wanted deleteCall.called to be %v, got %v", tc.shouldCallDelete, deleteCall.called)
			}
			if tc.shouldCallDelete {
				if deleteCall.key != tc.expectedHostname {
					t.Fatalf("wanted deleteCall.key to be %v, got %v", tc.expectedHostname, deleteCall.key)
				}
			}

			if tc.initStorage {
				dnsManager.DisposeCache()
			}
		})
	}
}
