package gockle

import (
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/gocql/gocql"
	"github.com/maraino/go-mock"
)

const version = 4

const (
	ksCreate   = "create keyspace gockle_test with replication = {'class': 'SimpleStrategy', 'replication_factor': 1};"
	ksDrop     = "drop keyspace gockle_test"
	ksDropIf   = "drop keyspace if exists gockle_test"
	rowInsert  = "insert into gockle_test.test (id, n) values (1, 2)"
	rowInsert2 = "insert into gockle_test.test (id, n) values (3, 4)"
	tabCreate  = "create table gockle_test.test(id int primary key, n int)"
	tabDrop    = "drop table gockle_test.test"
)

func TestNewSession(t *testing.T) {
	if a, e := NewSession(nil), (session{}); a != e {
		t.Errorf("Actual session %v, expected %v", a, e)
	}

	var c = gocql.NewCluster("localhost")

	c.ProtoVersion = version

	var s, err = c.CreateSession()

	if err != nil {
		t.Skip(err)
	}

	if a, e := NewSession(s), (session{s: s}); a != e {
		t.Errorf("Actual session %v, expected %v", a, e)
	}
}

func TestNewSimpleSession(t *testing.T) {
	if s, err := NewSimpleSession(); err == nil {
		t.Error("Actual no error, expected error")
	} else if s != nil {
		t.Errorf("Actual session %v, expected nil", s)
		s.Close()
	}

	if a, err := NewSimpleSession("localhost"); err != nil {
		t.Skip(err)
	} else if a == nil {
		t.Errorf("Actual session nil, expected not nil")
	} else {
		a.Close()
	}
}

func TestSessionMetadata(t *testing.T) {
	var exec = func(s Session, q string) {
		if err := s.Exec(q); err != nil {
			t.Fatalf("Actual error %v, expected no error", err)
		}
	}

	var s = newSession(t)

	exec(s, ksDropIf)
	exec(s, ksCreate)

	defer exec(s, ksDrop)

	exec(s, tabCreate)

	defer exec(s, tabDrop)

	s = newSession(t)

	if a, err := s.Tables("gockle_test"); err == nil {
		if e := ([]string{"test"}); !reflect.DeepEqual(a, e) {
			t.Fatalf("Actual tables %v, expected %v", a, e)
		}
	} else {
		t.Fatalf("Actual error %v, expected no error", err)
	}

	if _, err := s.Tables("gockle_test_invalid"); err == nil {
		t.Errorf("Actual no error, expected error")
	}

	s.Close()

	if _, err := s.Tables("gockle_test"); err == nil {
		t.Errorf("Actual no error, expected error")
	}

	s = newSession(t)

	if a, err := s.Columns("gockle_test", "test"); err == nil {
		var ts = map[string]gocql.Type{"id": gocql.TypeInt, "n": gocql.TypeInt}

		if la, le := len(a), len(ts); la == le {
			for n, at := range a {
				if et, ok := ts[n]; ok {
					if at.Type() != et {
						t.Fatalf("Actual type %v, expected %v", at, et)
					}
				} else {
					t.Fatalf("Actual name %v invalid, expected valid", n)
				}
			}
		} else {
			t.Fatalf("Actual count %v, expected %v", la, le)
		}
	} else {
		t.Fatalf("Actual error %v, expected no error", err)
	}

	if _, err := s.Columns("gockle_test", "invalid"); err == nil {
		t.Error("Actual no error, expected error")
	}

	s.Close()

	if _, err := s.Columns("gockle_test", "test"); err == nil {
		t.Error("Actual no error, expected error")
	}
}

func TestSessionMock(t *testing.T) {
	var m, e = &SessionMock{}, fmt.Errorf("e")

	testMock(t, m, &m.Mock, []struct {
		method    string
		arguments []interface{}
		results   []interface{}
	}{
		{"Close", nil, nil},
		{"Columns", []interface{}{"", ""}, []interface{}{map[string]gocql.TypeInfo(nil), nil}},
		{"Columns", []interface{}{"a", "b"}, []interface{}{map[string]gocql.TypeInfo{"c": gocql.NativeType{}}, e}},
		{"Batch", []interface{}{BatchKind(0)}, []interface{}{(*batch)(nil)}},
		{"Batch", []interface{}{BatchKind(1)}, []interface{}{&batch{}}},
		{"Exec", []interface{}{"", []interface{}(nil)}, []interface{}{nil}},
		{"Exec", []interface{}{"a", []interface{}{1}}, []interface{}{e}},
		{"Scan", []interface{}{"", []interface{}(nil), []interface{}(nil)}, []interface{}{nil}},
		{"Scan", []interface{}{"a", []interface{}{1}, []interface{}{1}}, []interface{}{e}},
		{"ScanIterator", []interface{}{"", []interface{}(nil)}, []interface{}{(*iterator)(nil)}},
		{"ScanIterator", []interface{}{"a", []interface{}{1}}, []interface{}{iterator{}}},
		{"ScanMap", []interface{}{"", map[string]interface{}(nil), []interface{}(nil)}, []interface{}{nil}},
		{"ScanMap", []interface{}{"a", map[string]interface{}{"b": 2}, []interface{}{1}}, []interface{}{e}},
		{"ScanMapSlice", []interface{}{"", []interface{}(nil)}, []interface{}{[]map[string]interface{}(nil), nil}},
		{"ScanMapSlice", []interface{}{"a", []interface{}{1}}, []interface{}{[]map[string]interface{}{{"b": 2}}, e}},
		{"ScanMapTx", []interface{}{"", map[string]interface{}(nil), []interface{}(nil)}, []interface{}{false, nil}},
		{"ScanMapTx", []interface{}{"a", map[string]interface{}{"b": 2}, []interface{}{1}}, []interface{}{true, e}},
		{"Tables", []interface{}{""}, []interface{}{[]string(nil), nil}},
		{"Tables", []interface{}{"a"}, []interface{}{[]string{"b"}, e}},
		{"Query", []interface{}{"a", []interface{}{1}}, []interface{}{query{}}},
	})
}

func TestSessionQuery(t *testing.T) {
	var s = newSession(t)

	defer s.Close()

	var exec = func(q string) {
		if err := s.Exec(q); err != nil {
			t.Fatalf("Actual error %v, expected no error", err)
		}
	}

	exec(ksDropIf)
	exec(ksCreate)

	defer exec(ksDrop)

	exec(tabCreate)

	defer exec(tabDrop)

	exec(rowInsert)

	// Batch
	if s.Batch(BatchKind(0)) == nil {
		t.Error("Actual batch nil, expected not nil")
	}

	// ScanIterator
	if s.ScanIterator("select * from gockle_test.test") == nil {
		t.Error("Actual iterator nil, expected not nil")
	}

	// Scan
	var id, n int

	if err := s.Scan("select id, n from gockle_test.test", []interface{}{&id, &n}); err == nil {
		if id != 1 {
			t.Errorf("Actual id %v, expected 1", id)
		}

		if n != 2 {
			t.Errorf("Actual n %v, expected 2", n)
		}
	} else {
		t.Errorf("Actual error %v, expected no error", err)
	}

	// ScanMap
	var am, em = map[string]interface{}{}, map[string]interface{}{"id": 1, "n": 2}

	if err := s.ScanMap("select id, n from gockle_test.test", am); err == nil {
		if !reflect.DeepEqual(am, em) {
			t.Errorf("Actual map %v, expected %v", am, em)
		}
	} else {
		t.Errorf("Actual error %v, expected no error", err)
	}

	// ScanMapTx
	am = map[string]interface{}{}

	if b, err := s.ScanMapTx("update gockle_test.test set n = 3 where id = 1 if n = 2", am); err == nil {
		if !b {
			t.Error("Actual applied false, expected true")
		}

		if l := len(am); l != 0 {
			t.Errorf("Actual length %v, expected 0", l)
		}
	} else {
		t.Errorf("Actual error %v, expected no error", err)
	}

	// ScanMapSlice
	var es = []map[string]interface{}{{"id": 1, "n": 3}}

	if as, err := s.ScanMapSlice("select * from gockle_test.test"); err == nil {
		if !reflect.DeepEqual(as, es) {
			t.Errorf("Actual rows %v, expected %v", as, es)
		}
	} else {
		t.Errorf("Actual error %v, expected no error", err)
	}
}

func newSession(t *testing.T) Session {
	var c = gocql.NewCluster("localhost")

	c.ProtoVersion = version
	c.Timeout = 5 * time.Second

	var s, err = c.CreateSession()

	if err != nil {
		t.Skip(err)
	}

	return NewSession(s)
}

func testMock(t *testing.T, i interface{}, m *mock.Mock, tests []struct {
	method    string
	arguments []interface{}
	results   []interface{}
}) {
	var v = reflect.ValueOf(i)

	for _, test := range tests {
		t.Log("Test:", test)
		m.Reset()
		m.When(test.method, test.arguments...).Return(test.results...)

		var vs []reflect.Value

		for _, a := range test.arguments {
			vs = append(vs, reflect.ValueOf(a))
		}

		var method = v.MethodByName(test.method)

		if method.Type().IsVariadic() {
			vs = method.CallSlice(vs)
		} else {
			vs = method.Call(vs)
		}

		var is []interface{}

		for _, v := range vs {
			is = append(is, v.Interface())
		}

		if !reflect.DeepEqual(is, test.results) {
			t.Errorf("Actual %v, expected %v", is, test.results)
		}
	}
}
