package bsearch

import (
	"fmt"
	"strings"
	"testing"

	//"github.com/rs/zerolog"
	//"github.com/rs/zerolog/log"
	"github.com/stretchr/testify/assert"
)

// Test Searcher.Line() using testdata/rdns1.csv, existing keys
func TestSearcherLine1(t *testing.T) {
	var tests = []struct {
		key    string
		expect string
	}{
		{"001.000.128.000", "001.000.128.000,node-0.pool-1-0.dynamic.totinternet.net,202003,totinternet.net"},
		{"001.034.164.000", "001.034.164.000,1-34-164-0.HINET-IP.hinet.net,202003,hinet.net"},
		{"003.122.207.000", "003.122.207.000,ec2-3-122-207-0.eu-central-1.compute.amazonaws.com,202003,amazonaws.com"},
		{"003.126.183.000", "003.126.183.000,ec2-3-126-183-0.eu-central-1.compute.amazonaws.com,202003,amazonaws.com"},
		{"024.066.017.000", "024.066.017.000,S0106905851b9f0e0.rd.shawcable.net,202003,shawcable.net"},
		{"032.176.184.000", "032.176.184.000,mobile000.mycingular.net,202003,mycingular.net"},
		{"223.252.003.000", "223.252.003.000,223-252-3-0.as45671.net,202003,as45671.net"},
	}

	ensureIndex(t, "rdns1.csv")
	s, err := NewSearcher("testdata/rdns1.csv")
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		line, err := s.Line([]byte(tc.key))
		if err != nil {
			t.Fatalf("%s: %s\n", tc.key, err.Error())
		}
		if string(line) != tc.expect {
			t.Errorf("%q => %q\n   expected %q\n", tc.key, line, tc.expect)
		}
	}
}

// Test Searcher.Line() using testdata/domains1.csv (no header)
func TestSearcherLine2(t *testing.T) {
	var tests = []struct {
		key    string
		expect string
	}{
		{"aaa.com", ""},
		{"accuweather.com", "accuweather.com,567"},
		{"adweek.com", "adweek.com,305"},
		{"evernote.com", "evernote.com,739"},
		{"etracker.com", "etracker.com,477"},
		{"matterport.com", "matterport.com,683"},
		{"openfusion.com.au", ""},
		{"zenfolio.com", "zenfolio.com,416"},
		{"zzz.com", ""},
	}

	ensureIndex(t, "domains1.csv")
	s, err := NewSearcher("testdata/domains1.csv")
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		line, err := s.Line([]byte(tc.key))
		if err != nil {
			if err != ErrNotFound || tc.expect != "" {
				t.Fatalf("%s: %s\n", tc.key, err.Error())
			}
		}
		if string(line) != tc.expect {
			t.Errorf("%q => %q\n   expected %q\n", tc.key, line, tc.expect)
		}
	}
}

// Test Searcher.Line() using testdata/domains2.csv (header)
func TestSearcherLine3(t *testing.T) {
	var tests = []struct {
		key    string
		expect string
	}{
		{"aaa.com", ""},
		{"accuweather.com", "accuweather.com,567"},
		{"adweek.com", "adweek.com,305"},
		{"evernote.com", "evernote.com,739"},
		{"etracker.com", "etracker.com,477"},
		{"matterport.com", "matterport.com,683"},
		{"openfusion.com.au", ""},
		{"zenfolio.com", "zenfolio.com,416"},
		{"zzz.com", ""},
	}

	ensureIndex(t, "domains2.csv")
	o := SearcherOptions{Header: true}
	/*
		zerolog.SetGlobalLevel(zerolog.TraceLevel)
		log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
		o.Logger = &log.Logger
	*/
	s, err := NewSearcherOptions("testdata/domains2.csv", o)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		line, err := s.Line([]byte(tc.key))
		if err != nil {
			if err != ErrNotFound || tc.expect != "" {
				t.Fatalf("%s: %s\n", tc.key, err.Error())
			}
		}
		assert.Equal(t, tc.expect, string(line), tc.key)
	}
}

// Test Searcher.Line() using testdata/foo.csv (header, duplicate keys)
func TestSearcherLineFoo(t *testing.T) {
	var tests = []struct {
		key    string
		expect string
	}{
		{"bar", "bar,1"},
		{"foo", "foo,2"},
	}

	ensureIndex(t, "foo.csv")
	o := SearcherOptions{Header: true}
	/*
		zerolog.SetGlobalLevel(zerolog.TraceLevel)
		log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
		o.Logger = &log.Logger
	*/
	s, err := NewSearcherOptions("testdata/foo.csv", o)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		line, err := s.Line([]byte(tc.key))
		assert.Nil(t, err)
		assert.Equal(t, tc.expect, string(line), tc.key)
	}
}

// Test Searcher.Lines() using testdata/alstom1.csv (no header)
func TestSearcherLines1(t *testing.T) {
	var tests = []struct {
		key    string
		expect string
	}{
		{"alstom.com", `alstom.com,alstom.com,SOA
alstom.com,alstom.com,ULT
`},
		{"alstom.com.au", "alstom.com.au,alstom.com,RED\n"},
		{"alstom.com.br", "alstom.com.br,alstom.com,RED\n"},
	}

	ensureIndex(t, "alstom1.csv")
	o := SearcherOptions{Header: false}
	/*
		zerolog.SetGlobalLevel(zerolog.TraceLevel)
		log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
		o.Logger = &log.Logger
	*/
	s, err := NewSearcherOptions("testdata/alstom1.csv", o)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		lines, err := s.Lines([]byte(tc.key))
		if err != nil {
			if err != ErrNotFound || tc.expect != "" {
				t.Fatalf("%s: %s\n", tc.key, err.Error())
			}
		}
		s := []string{}
		for _, line := range lines {
			s = append(s, string(line))
		}
		linesStr := strings.Join(s, "\n") + "\n"
		if linesStr != tc.expect {
			t.Errorf("%q => %q\n   expected %q\n", tc.key, linesStr, tc.expect)
		}
	}
}

// Test Searcher.Lines() using testdata/alstom2.csv (header)
func TestSearcherLines2(t *testing.T) {
	var tests = []struct {
		key    string
		expect string
	}{
		// alstom.com (includes last line of file)
		{"alstom.com", `alstom.com,alstom.com,SOA
alstom.com,alstom.com,ULT
`},
		{"alstom.com.au", "alstom.com.au,alstom.com,RED\n"},
		{"alstom.com.br", "alstom.com.br,alstom.com,RED\n"},
	}

	ensureIndex(t, "alstom2.csv")
	o := SearcherOptions{Header: true}
	s, err := NewSearcherOptions("testdata/alstom2.csv", o)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		lines, err := s.Lines([]byte(tc.key))
		if err != nil {
			if err != ErrNotFound || tc.expect != "" {
				t.Fatalf("%s: %s\n", tc.key, err.Error())
			}
		}
		s := []string{}
		for _, line := range lines {
			s = append(s, string(line))
		}
		linesStr := strings.Join(s, "\n") + "\n"
		if linesStr != tc.expect {
			t.Errorf("%q => %q\n   expected %q\n", tc.key, linesStr, tc.expect)
		}
	}
}

// Test Searcher.Lines() using testdata/alstom3.csv
// (with header, multiple blocks, next block 1)
func TestSearcherLinesMultiBlock1(t *testing.T) {
	var tests = []struct {
		key        string
		first_line string
		last_line  string
		line_count int
	}{
		{"alstom.com", "alstom.com,first", "alstom.com,last", 438},
	}

	ensureIndex(t, "alstom3.csv")
	o := SearcherOptions{Header: true}
	/*
		zerolog.SetGlobalLevel(zerolog.TraceLevel)
		log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
		o.Logger = &log.Logger
	*/
	s, err := NewSearcherOptions("testdata/alstom3.csv", o)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		lines, err := s.Lines([]byte(tc.key))
		if err != nil {
			t.Fatalf("%s: %s\n", tc.key, err.Error())
		}
		if len(lines) != tc.line_count {
			t.Errorf("%s: expected %d lines, got %d", tc.key, tc.line_count, len(lines))
		}
		if len(lines) > 0 {
			if string(lines[0]) != tc.first_line {
				t.Errorf("%q => first line %q\n   expected %q\n", tc.key, lines[0], tc.first_line)
			}
			if string(lines[len(lines)-1]) != tc.last_line {
				t.Errorf("%q => last line %q\n   expected %q\n", tc.key, lines[len(lines)-1], tc.last_line)
			}
		}
	}
}

// Test Searcher.Lines() using testdata/alstom3.csv
// (with header, multiple blocks, next block 2)
func TestSearcherLinesMultiBlock2(t *testing.T) {
	var tests = []struct {
		key        string
		first_line string
		last_line  string
	}{
		{"alstom.com", "alstom.com,first", "alstom.com,last"},
	}

	ensureIndex(t, "alstom4.csv")
	o := SearcherOptions{Header: true}
	s, err := NewSearcherOptions("testdata/alstom4.csv", o)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		lines, err := s.Lines([]byte(tc.key))
		if err != nil {
			t.Fatalf("%s: %s\n", tc.key, err.Error())
		}
		if len(lines) <= 2 {
			t.Fatalf("%s: expected N>2 lines, got %d\n", tc.key, len(lines))
		}
		if len(lines) > 0 {
			if string(lines[0]) != tc.first_line {
				t.Errorf("%q => first line %q\n   expected %q\n", tc.key, lines[0], tc.first_line)
			}
			if string(lines[len(lines)-1]) != tc.last_line {
				t.Errorf("%q => last line %q\n   expected %q\n", tc.key, lines[len(lines)-1], tc.last_line)
			}
		}
	}
}

// Test Searcher.Lines() (without header, multiple blocks, starting block 1)
func TestSearcherLinesFoo(t *testing.T) {
	var tests = []struct {
		key        string
		count      int
		first_line string
		last_line  string
	}{
		{"bar", 1, "bar,1", "bar,1"},
		{"foo", 9999, "foo,2", "foo,10000"},
	}

	ensureIndex(t, "foo.csv")
	o := SearcherOptions{Header: false}
	s, err := NewSearcherOptions("testdata/foo.csv", o)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	for _, tc := range tests {
		lines, err := s.Lines([]byte(tc.key))
		assert.Nil(t, err)
		assert.Equal(t, tc.count, len(lines), tc.key+" line count")
		assert.Equal(t, tc.first_line, string(lines[0]), tc.key+" first_line")
		assert.Equal(t, tc.last_line, string(lines[len(lines)-1]),
			tc.key+" last_line")
	}
}

// Benchmark Searcher.Lines()
func BenchmarkSearcherLines(b *testing.B) {
	bss, err := NewSearcher("testdata/rdns1.csv")
	if err != nil {
		b.Fatal(err)
	}
	defer bss.Close()
	prefix := []byte("162.")
	for i := 0; i < b.N; i++ {
		lines, err := bss.Lines(prefix)
		if err != nil {
			b.Fatal(err)
		}
		if len(lines) != 12 {
			b.Fatal(fmt.Errorf("Lines returned %d results, expected 12\n", len(lines)))
		}
	}
}