package parse

import (
	"os"
	"strings"
	"testing"
)

func TestStandardAddress(t *testing.T) {
	for i, test := range []struct {
		input      string
		host, port string
		shouldErr  bool
	}{
		{`localhost`, "localhost.", "53", false},
		{`localhost:1234`, "localhost.", "1234", false},
		{`localhost:`, "localhost.", "53", false},
		{`0.0.0.0`, "0.0.0.0.", "53", false},
		{`127.0.0.1:1234`, "127.0.0.1.", "1234", false},
		{`:1234`, ".", "1234", false},
		{`[::1]`, "::1.", "53", false},
		{`[::1]:1234`, "::1.", "1234", false},
		{`:`, ".", "53", false},
		{`localhost:http`, "localhost.", "http", false},
		{`localhost:https`, "localhost.", "https", false},
		{``, ".", "53", false},
		{`::1`, "::1.", "53", true},
		{`localhost::`, "localhost::.", "53", true},
		{`#$%@`, "#$%@.", "53", true},
	} {
		actual, err := standardAddress(test.input)

		if err != nil && !test.shouldErr {
			t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
		}
		if err == nil && test.shouldErr {
			t.Errorf("Test %d (%s): Expected error, but had none", i, test.input)
		}

		if actual.Host != test.host {
			t.Errorf("Test %d (%s): Expected host '%s', got '%s'", i, test.input, test.host, actual.Host)
		}
		if actual.Port != test.port {
			t.Errorf("Test %d (%s): Expected port '%s', got '%s'", i, test.input, test.port, actual.Port)
		}
	}
}

func TestParseOneAndImport(t *testing.T) {
	setupParseTests()

	testParseOne := func(input string) (ServerBlock, error) {
		p := testParser(input)
		p.Next() // parseOne doesn't call Next() to start, so we must
		err := p.parseOne()
		return p.block, err
	}

	for i, test := range []struct {
		input     string
		shouldErr bool
		addresses []address
		tokens    map[string]int // map of directive name to number of tokens expected
	}{
		{`localhost`, false, []address{
			{"localhost", "localhost.", "53"},
		}, map[string]int{}},

		{`localhost
		  dir1`, false, []address{
			{"localhost", "localhost.", "53"},
		}, map[string]int{
			"dir1": 1,
		}},

		{`localhost:1234
		  dir1 foo bar`, false, []address{
			{"localhost:1234", "localhost.", "1234"},
		}, map[string]int{
			"dir1": 3,
		}},

		{`localhost {
		    dir1
		  }`, false, []address{
			{"localhost", "localhost.", "53"},
		}, map[string]int{
			"dir1": 1,
		}},

		{`localhost:1234 {
		    dir1 foo bar
		    dir2
		  }`, false, []address{
			{"localhost:1234", "localhost.", "1234"},
		}, map[string]int{
			"dir1": 3,
			"dir2": 1,
		}},

		{`host1:80, host2.com
		  dir1 foo bar
		  dir2 baz`, false, []address{
			{"host1:80", "host1.", "80"},
			{"host2.com", "host2.com.", "53"},
		}, map[string]int{
			"dir1": 3,
			"dir2": 2,
		}},

		{`127.0.0.1
		  dir1 {
		    bar baz
		  }
		  dir2 {
		    foo bar
		  }`, false, []address{
			{"127.0.0.1", "127.0.0.1.", "53"},
		}, map[string]int{
			"dir1": 5,
			"dir2": 5,
		}},

		{`127.0.0.1
		  unknown_directive`, true, []address{
			{"127.0.0.1", "127.0.0.1.", "53"},
		}, map[string]int{}},

		{`localhost
		  dir1 {
		    foo`, true, []address{
			{"localhost", "localhost.", "53"},
		}, map[string]int{
			"dir1": 3,
		}},

		{`localhost
		  dir1 {
		  }`, false, []address{
			{"localhost", "localhost.", "53"},
		}, map[string]int{
			"dir1": 3,
		}},

		{`localhost
		  dir1 {
		  } }`, true, []address{
			{"localhost", "localhost.", "53"},
		}, map[string]int{
			"dir1": 3,
		}},

		{`localhost
		  dir1 {
		    nested {
		      foo
		    }
		  }
		  dir2 foo bar`, false, []address{
			{"localhost", "localhost.", "53"},
		}, map[string]int{
			"dir1": 7,
			"dir2": 3,
		}},

		{``, false, []address{}, map[string]int{}},

		{`localhost
		  dir1 arg1
		  import import_test1.txt`, false, []address{
			{"localhost", "localhost.", "53"},
		}, map[string]int{
			"dir1": 2,
			"dir2": 3,
			"dir3": 1,
		}},

		{`import import_test2.txt`, false, []address{
			{"host1", "host1.", "53"},
		}, map[string]int{
			"dir1": 1,
			"dir2": 2,
		}},

		{`import import_test1.txt import_test2.txt`, true, []address{}, map[string]int{}},

		{`import not_found.txt`, true, []address{}, map[string]int{}},

		{`""`, false, []address{}, map[string]int{}},

		{``, false, []address{}, map[string]int{}},
	} {
		result, err := testParseOne(test.input)

		if test.shouldErr && err == nil {
			t.Errorf("Test %d: Expected an error, but didn't get one", i)
		}
		if !test.shouldErr && err != nil {
			t.Errorf("Test %d: Expected no error, but got: %v", i, err)
		}

		if len(result.Addresses) != len(test.addresses) {
			t.Errorf("Test %d: Expected %d addresses, got %d",
				i, len(test.addresses), len(result.Addresses))
			continue
		}
		for j, addr := range result.Addresses {
			if addr.Host != test.addresses[j].Host {
				t.Errorf("Test %d, address %d: Expected host to be '%s', but was '%s'",
					i, j, test.addresses[j].Host, addr.Host)
			}
			if addr.Port != test.addresses[j].Port {
				t.Errorf("Test %d, address %d: Expected port to be '%s', but was '%s'",
					i, j, test.addresses[j].Port, addr.Port)
			}
		}

		if len(result.Tokens) != len(test.tokens) {
			t.Errorf("Test %d: Expected %d directives, had %d",
				i, len(test.tokens), len(result.Tokens))
			continue
		}
		for directive, tokens := range result.Tokens {
			if len(tokens) != test.tokens[directive] {
				t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d",
					i, directive, test.tokens[directive], len(tokens))
				continue
			}
		}
	}
}

func TestParseAll(t *testing.T) {
	setupParseTests()

	for i, test := range []struct {
		input     string
		shouldErr bool
		addresses [][]address // addresses per server block, in order
	}{
		{`localhost`, false, [][]address{
			{{"localhost", "localhost.", "53"}},
		}},

		{`localhost:1234`, false, [][]address{
			{{"localhost:1234", "localhost.", "1234"}},
		}},

		{`localhost:1234 {
		  }
		  localhost:2015 {
		  }`, false, [][]address{
			{{"localhost:1234", "localhost.", "1234"}},
			{{"localhost:2015", "localhost.", "2015"}},
		}},

		{`localhost:1234, host2`, false, [][]address{
			{{"localhost:1234", "localhost.", "1234"}, {"host2", "host2.", "53"}},
		}},

		{`localhost:1234, http://host2,`, true, [][]address{}},

		{`import import_glob*.txt`, false, [][]address{
			{{"glob0.host0", "glob0.host0.", "53"}},
			{{"glob0.host1", "glob0.host1.", "53"}},
			{{"glob1.host0", "glob1.host0.", "53"}},
			{{"glob2.host0", "glob2.host0.", "53"}},
		}},
	} {
		p := testParser(test.input)
		blocks, err := p.parseAll()

		if test.shouldErr && err == nil {
			t.Errorf("Test %d: Expected an error, but didn't get one", i)
		}
		if !test.shouldErr && err != nil {
			t.Errorf("Test %d: Expected no error, but got: %v", i, err)
		}

		if len(blocks) != len(test.addresses) {
			t.Errorf("Test %d: Expected %d server blocks, got %d",
				i, len(test.addresses), len(blocks))
			continue
		}
		for j, block := range blocks {
			if len(block.Addresses) != len(test.addresses[j]) {
				t.Errorf("Test %d: Expected %d addresses in block %d, got %d",
					i, len(test.addresses[j]), j, len(block.Addresses))
				continue
			}
			for k, addr := range block.Addresses {
				if addr.Host != test.addresses[j][k].Host {
					t.Errorf("Test %d, block %d, address %d: Expected host to be '%s', but was '%s'",
						i, j, k, test.addresses[j][k].Host, addr.Host)
				}
				if addr.Port != test.addresses[j][k].Port {
					t.Errorf("Test %d, block %d, address %d: Expected port to be '%s', but was '%s'",
						i, j, k, test.addresses[j][k].Port, addr.Port)
				}
			}
		}
	}
}

func TestEnvironmentReplacement(t *testing.T) {
	setupParseTests()

	os.Setenv("PORT", "8080")
	os.Setenv("ADDRESS", "servername.com")
	os.Setenv("FOOBAR", "foobar")

	// basic test; unix-style env vars
	p := testParser(`{$ADDRESS}`)
	blocks, _ := p.parseAll()
	if actual, expected := blocks[0].Addresses[0].Host, "servername.com."; expected != actual {
		t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
	}

	// multiple vars per token
	p = testParser(`{$ADDRESS}:{$PORT}`)
	blocks, _ = p.parseAll()
	if actual, expected := blocks[0].Addresses[0].Host, "servername.com."; expected != actual {
		t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
	}
	if actual, expected := blocks[0].Addresses[0].Port, "8080"; expected != actual {
		t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
	}

	// windows-style var and unix style in same token
	p = testParser(`{%ADDRESS%}:{$PORT}`)
	blocks, _ = p.parseAll()
	if actual, expected := blocks[0].Addresses[0].Host, "servername.com."; expected != actual {
		t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
	}
	if actual, expected := blocks[0].Addresses[0].Port, "8080"; expected != actual {
		t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
	}

	// reverse order
	p = testParser(`{$ADDRESS}:{%PORT%}`)
	blocks, _ = p.parseAll()
	if actual, expected := blocks[0].Addresses[0].Host, "servername.com."; expected != actual {
		t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
	}
	if actual, expected := blocks[0].Addresses[0].Port, "8080"; expected != actual {
		t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
	}

	// env var in server block body as argument
	p = testParser(":{%PORT%}\ndir1 {$FOOBAR}")
	blocks, _ = p.parseAll()
	if actual, expected := blocks[0].Addresses[0].Port, "8080"; expected != actual {
		t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
	}
	if actual, expected := blocks[0].Tokens["dir1"][1].text, "foobar"; expected != actual {
		t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
	}

	// combined windows env vars in argument
	p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
	blocks, _ = p.parseAll()
	if actual, expected := blocks[0].Tokens["dir1"][1].text, "servername.com/foobar"; expected != actual {
		t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
	}

	// malformed env var (windows)
	p = testParser(":1234\ndir1 {%ADDRESS}")
	blocks, _ = p.parseAll()
	if actual, expected := blocks[0].Tokens["dir1"][1].text, "{%ADDRESS}"; expected != actual {
		t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
	}

	// malformed (non-existent) env var (unix)
	p = testParser(`:{$PORT$}`)
	blocks, _ = p.parseAll()
	if actual, expected := blocks[0].Addresses[0].Port, "53"; expected != actual {
		t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
	}

	// in quoted field
	p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
	blocks, _ = p.parseAll()
	if actual, expected := blocks[0].Tokens["dir1"][1].text, "Test foobar test"; expected != actual {
		t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
	}
}

func setupParseTests() {
	// Set up some bogus directives for testing
	ValidDirectives = map[string]struct{}{
		"dir1": {},
		"dir2": {},
		"dir3": {},
	}
}

func testParser(input string) parser {
	buf := strings.NewReader(input)
	p := parser{Dispenser: NewDispenser("Test", buf), checkDirectives: true}
	return p
}
