package datasource

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/onsi/gomega"

	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/datasource/view"
	"github.com/pingcap-inc/clinic/pkg/pagination"
)

type SmartDataAccessorTest struct {
	data []string
}

func (sat *SmartDataAccessorTest) ElementType() reflect.Type {
	return reflect.TypeOf("")
}

func (sat *SmartDataAccessorTest) Accessor() func(ctx *session.CtxUser, query view.QueryFilter) (any, int64, error) {
	return func(ctx *session.CtxUser, query view.QueryFilter) (any, int64, error) {
		startIndex := pagination.GetStartIndex(query.Page, query.Limit)
		if startIndex >= len(sat.data) {
			return nil, int64(len(sat.data)), nil
		}
		return sat.data[startIndex:min(startIndex+query.Limit, len(sat.data))], int64(len(sat.data)), nil
	}
}

func TestJoin1(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	// integer multiples of limit
	t1, t2, expectValues := initTestData(20, 30)

	for i := 0; i < 10; i++ {
		pq := view.QueryFilter{
			Page:  i + 1,
			Limit: 10,
		}

		arr := make([]string, 0)
		total, err := joinDataSourceToSlice(t1.Accessor(), t2.Accessor(), nil, pq, &arr)
		g.Expect(err).To(gomega.Succeed())
		g.Expect(total).To(gomega.Equal(int64(len(t1.data) + len(t2.data))))
		if i <= 4 {
			g.Expect(len(arr)).To(gomega.Equal(pq.Limit))
			g.Expect(arr).To(gomega.ContainElements(expectValues[(pq.Page-1)*pq.Limit : (pq.Page-1)*pq.Limit+pq.Limit]))
		} else {
			g.Expect(len(arr)).To(gomega.Equal(0))
		}
	}

	t1, t2, expectValues = initTestData(3, 33)
	for i := 0; i < 10; i++ {
		pq := view.QueryFilter{
			Page:  i + 1,
			Limit: 10,
		}

		arr := make([]string, 0)
		total, err := joinDataSourceToSlice(t1.Accessor(), t2.Accessor(), nil, pq, &arr)
		g.Expect(err).To(gomega.Succeed())
		g.Expect(total).To(gomega.Equal(int64(len(t1.data) + len(t2.data))))
		if i < 3 {
			g.Expect(len(arr)).To(gomega.Equal(pq.Limit))
			g.Expect(arr).To(gomega.ContainElements(expectValues[(pq.Page-1)*pq.Limit : (pq.Page-1)*pq.Limit+pq.Limit]))
		} else if i == 3 {
			g.Expect(len(arr)).To(gomega.Equal(6))
			g.Expect(arr).To(gomega.ContainElements(expectValues[30:]))
		} else {
			g.Expect(len(arr)).To(gomega.Equal(0))
		}
	}

	t1, t2, expectValues = initTestData(33, 3)
	for i := 0; i < 10; i++ {
		pq := view.QueryFilter{
			Page:  i + 1,
			Limit: 10,
		}

		arr := make([]string, 0)
		total, err := joinDataSourceToSlice(t1.Accessor(), t2.Accessor(), nil, pq, &arr)
		g.Expect(err).To(gomega.Succeed())
		g.Expect(total).To(gomega.Equal(int64(len(t1.data) + len(t2.data))))
		if i < 3 {
			g.Expect(len(arr)).To(gomega.Equal(pq.Limit))
			g.Expect(arr).To(gomega.ContainElements(expectValues[(pq.Page-1)*pq.Limit : (pq.Page-1)*pq.Limit+pq.Limit]))
		} else if i == 3 {
			g.Expect(len(arr)).To(gomega.Equal(6))
			g.Expect(arr).To(gomega.ContainElements(expectValues[30:]))
		} else {
			g.Expect(len(arr)).To(gomega.Equal(0))
		}
	}

	t1, t2, expectValues = initTestData(3, 4)
	for i := 0; i < 10; i++ {
		pq := view.QueryFilter{
			Page:  i + 1,
			Limit: 10,
		}

		arr := make([]string, 0)
		total, err := joinDataSourceToSlice(t1.Accessor(), t2.Accessor(), nil, pq, &arr)
		g.Expect(err).To(gomega.Succeed())
		g.Expect(total).To(gomega.Equal(int64(len(t1.data) + len(t2.data))))
		if i == 0 {
			g.Expect(len(arr)).To(gomega.Equal(7))
			g.Expect(arr).To(gomega.ContainElements(expectValues))
		} else {
			g.Expect(len(arr)).To(gomega.Equal(0))
		}
	}

	t1, t2, _ = initTestData(0, 0)
	for i := 0; i < 10; i++ {
		pq := view.QueryFilter{
			Page:  i + 1,
			Limit: 10,
		}

		arr := make([]string, 0)
		total, err := joinDataSourceToSlice(t1.Accessor(), t2.Accessor(), nil, pq, &arr)
		g.Expect(err).To(gomega.Succeed())
		g.Expect(total).To(gomega.Equal(int64(len(t1.data) + len(t2.data))))
		g.Expect(len(arr)).To(gomega.Equal(0))
	}

	t1, t2, expectValues = initTestData(2, 0)
	for i := 0; i < 10; i++ {
		pq := view.QueryFilter{
			Page:  i + 1,
			Limit: 10,
		}

		arr := make([]string, 0)
		total, err := joinDataSourceToSlice(t1.Accessor(), t2.Accessor(), nil, pq, &arr)
		g.Expect(err).To(gomega.Succeed())
		g.Expect(total).To(gomega.Equal(int64(len(t1.data) + len(t2.data))))
		if i == 0 {
			g.Expect(len(arr)).To(gomega.Equal(2))
			g.Expect(arr).To(gomega.ContainElements(expectValues))
		} else {
			g.Expect(len(arr)).To(gomega.Equal(0))
		}
	}
}

func initTestData(len1, len2 int) (*SmartDataAccessorTest, *SmartDataAccessorTest, []string) {
	t1 := &SmartDataAccessorTest{
		data: getValue(len1, "t1"),
	}
	t2 := &SmartDataAccessorTest{
		data: getValue(len2, "t2"),
	}

	expectedValue := make([]string, 0)
	for i := 0; i < len(t1.data); i++ {
		expectedValue = append(expectedValue, t1.data[i])
	}
	for i := 0; i < len(t2.data); i++ {
		expectedValue = append(expectedValue, t2.data[i])
	}

	return t1, t2, expectedValue
}

func TestCalculatePages(t *testing.T) {
	g := gomega.NewGomegaWithT(t)

	for i := 1; i < 10; i++ {
		pages := calculatePages(20, view.QueryFilter{
			Page:  i,
			Limit: 10,
		})

		if i <= 2 {
			g.Expect(len(pages)).To(gomega.Equal(0))
		} else {
			g.Expect(len(pages)).To(gomega.Equal(1))
			g.Expect(pages[len(pages)-1]).To(gomega.Equal(i - 2))
		}
	}

	for i := 1; i < 10; i++ {
		pages := calculatePages(3, view.QueryFilter{
			Page:  i,
			Limit: 10,
		})

		if i == 1 {
			g.Expect(len(pages)).To(gomega.Equal(1))
			g.Expect(pages[len(pages)-1]).To(gomega.Equal(1))
		} else {
			g.Expect(len(pages)).To(gomega.Equal(2))

			g.Expect(pages[0]).To(gomega.Equal(i - 1))
			g.Expect(pages[1]).To(gomega.Equal(i))
		}
	}

	for i := 1; i < 10; i++ {
		pages := calculatePages(23, view.QueryFilter{
			Page:  i,
			Limit: 10,
		})

		if i <= 2 {
			g.Expect(len(pages)).To(gomega.Equal(0))
		} else if i == 3 {
			g.Expect(len(pages)).To(gomega.Equal(1))
			g.Expect(pages[len(pages)-1]).To(gomega.Equal(1))
		} else {
			g.Expect(len(pages)).To(gomega.Equal(2))

			g.Expect(pages[0]).To(gomega.Equal(i - 3))
			g.Expect(pages[1]).To(gomega.Equal(i - 2))
		}
	}

	//pages = calculatePages(23, common.PaginationQuery{
	//	Page: 3,
	//	Limit: 10,
	//})
	//g.Expect(len(pages)).To(gomega.Equal(1))
	//g.Expect(pages[len(pages) - 1]).To(gomega.Equal(1))
}

func getValue(len int, prefix string) []string {
	arr := make([]string, 0)
	for i := 0; i < len; i++ {
		arr = append(arr, fmt.Sprintf("%s-%d", prefix, i))
	}

	return arr
}
