package ramdb

import (
	"fmt"
	"sort"
	"sync"
	"testing"

	"github.com/google/btree"
	"github.com/stretchr/testify/assert"
)

func TestTable_Get(t *testing.T) {
	tests := []struct {
		test           string
		tableConfig    func() *table
		expectedRecord *record
		expectedError  error
	}{
		{
			test: "it should return ErrNoTable if an invalid table is supplied",
			tableConfig: func() *table {
				return &table{}
			},
			expectedError: ErrNoTable,
		},
		{
			test: "it should return ErrNoIndex if no index exists for column",
			tableConfig: func() *table {
				return &table{
					exists: true,
				}
			},
			expectedError: ErrNoIndex,
		},
		{
			test: "it should return ErrNoRecord if no record was found for key",
			tableConfig: func() *table {
				return &table{
					exists: true,
					indexes: map[string]*index{
						"test_column": &index{
							tree: btree.New(5),
						},
					},
				}
			},
			expectedError: ErrNoRecord,
		},
		{
			test: "it should return a record when one is found",
			tableConfig: func() *table {
				tbl := &table{
					exists: true,
					indexes: map[string]*index{
						"test_column": &index{
							tree: btree.New(5),
						},
					},
				}
				rec, err := NewRecord("test_key", "test_column", struct{}{})
				if err != nil {
					t.Error(err)
				}
				tbl.indexes["test_column"].tree.ReplaceOrInsert(rec)
				return tbl
			},
			expectedRecord: &record{
				serialized: []uint8{0x7b, 0x7d},
				key:        "test_key",
				keyColumn:  "test_column",
				id:         0x92488e1e3eeecdf9,
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.test, func(t *testing.T) {
			tbl := tc.tableConfig()

			record, err := tbl.Get("test_column", "test_key")

			assert.Equal(t, tc.expectedRecord, record)
			assert.Equal(t, tc.expectedError, err)
		})
	}
}

func TestTable_Select(t *testing.T) {
	tests := []struct {
		test            string
		tableConfig     func() (*table, []*record)
		expectedRecords []*record
		expectedError   error
	}{
		{
			test: "it should return ErrNoTable if an invalid table is supplied",
			tableConfig: func() (*table, []*record) {
				return &table{
					mutex: &sync.Mutex{},
				}, nil
			},
			expectedError: ErrNoTable,
		},
		{
			test: "it should return ErrNoIndex if no index exists for column",
			tableConfig: func() (*table, []*record) {
				return &table{
					exists:  true,
					mutex:   &sync.Mutex{},
					indexes: make(map[string]*index),
				}, nil
			},
			expectedError: ErrNoIndex,
		},
		{
			test: "it should return all records in the database",
			tableConfig: func() (*table, []*record) {
				tbl := &table{
					exists: true,
					mutex:  &sync.Mutex{},
					indexes: map[string]*index{
						"test_column": &index{
							tree: btree.New(5),
						},
					},
				}

				expectedRecords := make([]*record, 0)
				for i := 0; i < 10; i++ {
					key := fmt.Sprintf("key-%d", i)
					rec, err := NewRecord(key, "test_column", struct{}{})
					if err != nil {
						t.Error(err)
					}
					tbl.indexes["test_column"].tree.ReplaceOrInsert(rec)
					expectedRecords = append(expectedRecords, rec)
				}

				sort.Slice(expectedRecords, func(a, b int) bool {
					return expectedRecords[a].id < expectedRecords[b].id
				})

				return tbl, expectedRecords
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.test, func(t *testing.T) {
			tbl, expectedRecords := tc.tableConfig()

			rr, err := tbl.Select("test_column")

			assert.Equal(t, expectedRecords, rr)
			assert.Equal(t, tc.expectedError, err)
		})
	}
}

func TestTable_Insert(t *testing.T) {
	tests := []struct {
		test          string
		tableConfig   func() *table
		expectedError error
	}{
		{
			test: "it should return ErrNoTable if an invalid table is supplied",
			tableConfig: func() *table {
				return &table{
					mutex: &sync.Mutex{},
				}
			},
			expectedError: ErrNoTable,
		},
		{
			test: "it should return ErrNoIndex if no index exists for column",
			tableConfig: func() *table {
				return &table{
					exists:  true,
					mutex:   &sync.Mutex{},
					indexes: make(map[string]*index),
				}
			},
			expectedError: ErrNoIndex,
		},
		{
			test: "it should return ErrRecordExists if a record with key exists",
			tableConfig: func() *table {
				tbl := &table{
					exists: true,
					mutex:  &sync.Mutex{},
					indexes: map[string]*index{
						"test_column": &index{
							tree: btree.New(5),
						},
					},
				}

				rec, err := NewRecord("test_key", "test_column", struct{}{})
				if err != nil {
					t.Error(err)
				}
				tbl.indexes["test_column"].tree.ReplaceOrInsert(rec)

				return tbl
			},
			expectedError: ErrRecordExists,
		},
		{
			test: "it should return no error if successful",
			tableConfig: func() *table {
				return &table{
					exists: true,
					mutex:  &sync.Mutex{},
					indexes: map[string]*index{
						"test_column": &index{
							tree: btree.New(5),
						},
					},
				}
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.test, func(t *testing.T) {
			tbl := tc.tableConfig()
			rec, err := NewRecord("test_key", "test_column", struct{}{})
			if err != nil {
				t.Error(err)
			}

			err = tbl.Insert(rec)

			assert.Equal(t, tc.expectedError, err)
		})
	}
}

func TestTable_Delete(t *testing.T) {
	tests := []struct {
		test          string
		tableConfig   func() *table
		expectedError error
	}{
		{
			test: "it should return ErrNoTable if an invalid table is supplied",
			tableConfig: func() *table {
				return &table{
					mutex: &sync.Mutex{},
				}
			},
			expectedError: ErrNoTable,
		},
		{
			test: "it should return ErrNoIndex if no index exists for column",
			tableConfig: func() *table {
				return &table{
					exists:  true,
					mutex:   &sync.Mutex{},
					indexes: make(map[string]*index),
				}
			},
			expectedError: ErrNoIndex,
		},
		{
			test: "it should return ErrNoRecord if the record does not exist",
			tableConfig: func() *table {
				return &table{
					exists: true,
					mutex:  &sync.Mutex{},
					indexes: map[string]*index{
						"test_column": &index{
							tree: btree.New(5),
						},
					},
				}
			},
			expectedError: ErrNoRecord,
		},
		{
			test: "it should return no error if the item was deleted",
			tableConfig: func() *table {
				tbl := &table{
					exists: true,
					mutex:  &sync.Mutex{},
					indexes: map[string]*index{
						"test_column": &index{
							tree: btree.New(5),
						},
					},
				}
				rec, err := NewRecord("test_key", "test_column", struct{}{})
				if err != nil {
					t.Error(err)
				}

				tbl.indexes["test_column"].tree.ReplaceOrInsert(rec)
				return tbl
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.test, func(t *testing.T) {
			tbl := tc.tableConfig()
			rec, err := NewRecord("test_key", "test_column", struct{}{})
			if err != nil {
				t.Error(err)
			}

			err = tbl.Delete(rec)

			assert.Equal(t, tc.expectedError, err)
		})
	}
}
