package home

import (
	"fmt"
	"testing"

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

// any is a convenient alias for interface{}.
type any = interface{}

// object is a convenient alias for map[string]interface{}.
type object = map[string]any

func TestUpgradeSchema1to2(t *testing.T) {
	diskConf := testDiskConf(1)

	err := upgradeSchema1to2(&diskConf)
	require.Nil(t, err)

	require.Equal(t, diskConf["schema_version"], 2)

	_, ok := diskConf["coredns"]
	require.False(t, ok)

	dnsMap, ok := diskConf["dns"]
	require.True(t, ok)

	oldDNSConf := convertToObject(t, testDNSConf(1))
	newDNSConf := convertToObject(t, dnsMap)
	assert.Equal(t, oldDNSConf, newDNSConf)

	oldExcludedEntries := []string{"coredns", "schema_version"}
	newExcludedEntries := []string{"dns", "schema_version"}
	oldDiskConf := testDiskConf(1)
	assertEqualExcept(t, oldDiskConf, diskConf, oldExcludedEntries, newExcludedEntries)
}

func TestUpgradeSchema2to3(t *testing.T) {
	diskConf := testDiskConf(2)

	err := upgradeSchema2to3(&diskConf)
	require.Nil(t, err)

	require.Equal(t, diskConf["schema_version"], 3)

	dnsMap, ok := diskConf["dns"]
	require.True(t, ok)

	newDNSConf := convertToObject(t, dnsMap)
	bootstrapDNS := newDNSConf["bootstrap_dns"]
	switch v := bootstrapDNS.(type) {
	case []string:
		require.Len(t, v, 1)
		require.Equal(t, "8.8.8.8:53", v[0])
	default:
		t.Fatalf("wrong type for bootsrap dns: %T", v)
	}

	excludedEntries := []string{"bootstrap_dns"}
	oldDNSConf := convertToObject(t, testDNSConf(2))
	assertEqualExcept(t, oldDNSConf, newDNSConf, excludedEntries, excludedEntries)

	excludedEntries = []string{"dns", "schema_version"}
	oldDiskConf := testDiskConf(2)
	assertEqualExcept(t, oldDiskConf, diskConf, excludedEntries, excludedEntries)
}

func convertToObject(t *testing.T, oldConf any) (newConf object) {
	t.Helper()

	switch v := oldConf.(type) {
	case map[any]any:
		newConf = make(object, len(v))
		for key, value := range v {
			newConf[fmt.Sprint(key)] = value
		}
	case object:
		newConf = make(object, len(v))
		for key, value := range v {
			newConf[key] = value
		}
	default:
		t.Fatalf("dns configuration is not a map, got %T", oldConf)
	}

	return newConf
}

// assertEqualExcept removes entries from configs and compares them.
func assertEqualExcept(t *testing.T, oldConf, newConf object, oldKeys, newKeys []string) {
	t.Helper()

	for _, k := range oldKeys {
		delete(oldConf, k)
	}
	for _, k := range newKeys {
		delete(newConf, k)
	}

	assert.Equal(t, oldConf, newConf)
}

func testDiskConf(schemaVersion int) (diskConf object) {
	filters := []filter{
		{
			URL:        "https://filters.adtidy.org/android/filters/111_optimized.txt",
			Name:       "Latvian filter",
			RulesCount: 100,
		},
		{
			URL:        "https://easylist.to/easylistgermany/easylistgermany.txt",
			Name:       "Germany filter",
			RulesCount: 200,
		},
	}
	diskConf = object{
		"language":       "en",
		"filters":        filters,
		"user_rules":     []string{},
		"schema_version": schemaVersion,
		"bind_host":      "0.0.0.0",
		"bind_port":      80,
		"auth_name":      "name",
		"auth_pass":      "pass",
	}

	dnsConf := testDNSConf(schemaVersion)
	if schemaVersion > 1 {
		diskConf["dns"] = dnsConf
	} else {
		diskConf["coredns"] = dnsConf
	}

	return diskConf
}

// testDNSConf creates a DNS config for test the way gopkg.in/yaml.v2 would
// unmarshal it.  In YAML, keys aren't guaranteed to always only be strings.
func testDNSConf(schemaVersion int) (dnsConf map[any]any) {
	dnsConf = map[any]any{
		"port":                 53,
		"blocked_response_ttl": 10,
		"querylog_enabled":     true,
		"ratelimit":            20,
		"bootstrap_dns":        "8.8.8.8:53",
		"parental_sensitivity": 13,
		"ratelimit_whitelist":  []string{},
		"upstream_dns":         []string{"tls://1.1.1.1", "tls://1.0.0.1", "8.8.8.8"},
		"filtering_enabled":    true,
		"refuse_any":           true,
		"parental_enabled":     true,
		"bind_host":            "0.0.0.0",
		"protection_enabled":   true,
		"safesearch_enabled":   true,
		"safebrowsing_enabled": true,
	}

	if schemaVersion > 2 {
		dnsConf["bootstrap_dns"] = []string{"8.8.8.8:53"}
	}

	return dnsConf
}