// Copyright 2021 Molecula Corp. All rights reserved.
package sql

import (
	"fmt"
	"testing"

	"vitess.io/vitess/go/vt/sqlparser"
)

func TestParse(t *testing.T) {
	tests := []struct {
		sql     string
		expMask QueryMask
	}{
		{
			sql: "select _id from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartID,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select * from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartStar,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select fld from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartField,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select fld1, fld2 from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartFields,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select count(*) from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartCountStar,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select min(fld) from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartMinField,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select max(fld) from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartMaxField,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select sum(fld) from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartSumField,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select avg(fld) from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartAvgField,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select _id, count(*) from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartID | SelectPartCountStar,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select _id from tbl1, tbl2",
			expMask: QueryMask{
				SelectMask: SelectPartID,
				FromMask:   FromPartTables,
			},
		},
		{
			sql: "select _id from tbl1 INNER JOIN tbl2",
			expMask: QueryMask{
				SelectMask: SelectPartID,
				FromMask:   FromPartJoin,
			},
		},
		{
			sql: "select * from tbl where _id = 1",
			expMask: QueryMask{
				SelectMask: SelectPartStar,
				FromMask:   FromPartTable,
				WhereMask:  WherePartIDCondition,
			},
		},
		{
			sql: "select * from tbl where fld = 1",
			expMask: QueryMask{
				SelectMask: SelectPartStar,
				FromMask:   FromPartTable,
				WhereMask:  WherePartFieldCondition,
			},
		},
		{
			sql: "select * from tbl group by fld",
			expMask: QueryMask{
				SelectMask:  SelectPartStar,
				FromMask:    FromPartTable,
				GroupByMask: GroupByPartField,
			},
		},
		{
			sql: "select * from tbl group by fld1, fld2",
			expMask: QueryMask{
				SelectMask:  SelectPartStar,
				FromMask:    FromPartTable,
				GroupByMask: GroupByPartFields,
			},
		},
		{
			sql: "select fld, sum(fld) from tbl group by fld",
			expMask: QueryMask{
				SelectMask:  SelectPartField | SelectPartSumField,
				FromMask:    FromPartTable,
				GroupByMask: GroupByPartField,
			},
		},
		{
			sql: "select fld, sum(fld) from tbl group by fld having sum > 10",
			expMask: QueryMask{
				SelectMask:  SelectPartField | SelectPartSumField,
				FromMask:    FromPartTable,
				GroupByMask: GroupByPartField,
				HavingMask:  HavingPartCondition,
			},
		},
		{
			sql: "select fld from tbl order by fld",
			expMask: QueryMask{
				SelectMask:  SelectPartField,
				FromMask:    FromPartTable,
				OrderByMask: OrderByPartField,
			},
		},
		{
			sql: "select fld from tbl order by fld1, fld2",
			expMask: QueryMask{
				SelectMask:  SelectPartField,
				FromMask:    FromPartTable,
				OrderByMask: OrderByPartFields,
			},
		},
		{
			sql: "select fld from tbl limit 10",
			expMask: QueryMask{
				SelectMask: SelectPartField,
				FromMask:   FromPartTable,
				LimitMask:  LimitPartLimit,
			},
		},
		{
			sql: "select fld from tbl limit 10, 5",
			expMask: QueryMask{
				SelectMask: SelectPartField,
				FromMask:   FromPartTable,
				LimitMask:  LimitPartLimit | LimitPartOffset,
			},
		},
		{
			sql: "select distinct fld from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartDistinct | SelectPartField,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select count(*) from tbl where fld = 1",
			expMask: QueryMask{
				SelectMask: SelectPartCountStar,
				FromMask:   FromPartTable,
				WhereMask:  WherePartFieldCondition,
			},
		},
		{
			sql: "select count(*) from tbl where fld1 = 1 and fld2 = 2",
			expMask: QueryMask{
				SelectMask: SelectPartCountStar,
				FromMask:   FromPartTable,
				WhereMask:  WherePartMultiFieldCondition,
			},
		},
		{
			sql: "select count(*) from tbl where fld1 = 1 or fld2 = 2",
			expMask: QueryMask{
				SelectMask: SelectPartCountStar,
				FromMask:   FromPartTable,
				WhereMask:  WherePartMultiFieldCondition,
			},
		},
		{
			sql: "select _id from tbl where not fld = 1 limit 10",
			expMask: QueryMask{
				SelectMask: SelectPartID,
				FromMask:   FromPartTable,
				WhereMask:  WherePartFieldCondition,
				LimitMask:  LimitPartLimit,
			},
		},
		{
			sql: "select _id from tbl where fld between 1 and 3",
			expMask: QueryMask{
				SelectMask: SelectPartID,
				FromMask:   FromPartTable,
				WhereMask:  WherePartFieldCondition,
			},
		},
		{
			sql: "select _id from tbl where fld1 between 1 and 3 and fld2 = 2",
			expMask: QueryMask{
				SelectMask: SelectPartID,
				FromMask:   FromPartTable,
				WhereMask:  WherePartMultiFieldCondition,
			},
		},
		{
			sql: "select count(*) from tbl where fld is not null",
			expMask: QueryMask{
				SelectMask: SelectPartCountStar,
				FromMask:   FromPartTable,
				WhereMask:  WherePartFieldCondition,
			},
		},
		{
			sql: "select fld, count(*) from tbl group by fld",
			expMask: QueryMask{
				SelectMask:  SelectPartField | SelectPartCountStar,
				FromMask:    FromPartTable,
				GroupByMask: GroupByPartField,
			},
		},
		{
			sql: "select fld1, fld2, count(*) from grouper group by fld1, fld2",
			expMask: QueryMask{
				SelectMask:  SelectPartFields | SelectPartCountStar,
				FromMask:    FromPartTable,
				GroupByMask: GroupByPartFields,
			},
		},
		{
			sql: "select fld1, fld2, count(*) from tbl where fld1 = 1 group by fld1, fld2 limit 1",
			expMask: QueryMask{
				SelectMask:  SelectPartFields | SelectPartCountStar,
				FromMask:    FromPartTable,
				WhereMask:   WherePartFieldCondition,
				GroupByMask: GroupByPartFields,
				LimitMask:   LimitPartLimit,
			},
		},
		{
			sql: "select count(distinct fld) from tbl",
			expMask: QueryMask{
				SelectMask: SelectPartCountDistinctField,
				FromMask:   FromPartTable,
			},
		},
		{
			sql: "select count(*) from tbl1 INNER JOIN tbl2 ON tbl1._id = tbl2.bsi",
			expMask: QueryMask{
				SelectMask: SelectPartCountStar,
				FromMask:   FromPartJoin,
			},
		},
		{
			sql: "select count(*) from tbl1 INNER JOIN tbl2 ON tbl1._id = tbl2.bsi where tbl1.fld1 = 1 and tbl2.fld2 = 2",
			expMask: QueryMask{
				SelectMask: SelectPartCountStar,
				FromMask:   FromPartJoin,
				WhereMask:  WherePartMultiFieldCondition,
			},
		},
	}

	mapper := NewMapper()
	for i, test := range tests {
		t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
			_, mask, err := mapper.Parse(test.sql)
			if err != nil {
				t.Fatal(err)
			}

			if mask != test.expMask {
				t.Fatalf("expected mask: %v, but got: %v", test.expMask, mask)
			}
		})
	}
}

// This thing passes even if join is not implemented.
// TODO: make this actually a test
func TestSelectJoin(t *testing.T) {
	tests := []struct {
		sql string
	}{
		{
			// Count(Intersect(All(),Distinct(Row(grouperid!=null),index='joiner',field='grouperid')))
			sql: "select count(*) from grouper g INNER JOIN joiner j ON g._id = j.grouperid",
		},
		{
			// Intersect(All(),Distinct(Row(grouperid!=null),index='joiner',field='grouperid'))
			sql: "select _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid",
		},
		{
			// Intersect(Row(color='red'),Distinct(Row(grouperid!=null),index='joiner',field='grouperid'))
			sql: "select _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid where g.color = 'red'",
		},
		{
			// Intersect(All(),Distinct(Row(grouperid!=null),index='joiner',field='grouperid'))
			sql: "select _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid where g.color = 'red' and j.jointype = 2",
		},
	}

	mapper := NewMapper()
	for i, test := range tests {
		t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
			if m, err := mapper.MapSQL(test.sql); err != nil {
				t.Fatal(err)
			} else {
				if stmt, ok := m.Statement.(*sqlparser.Select); !ok {
					t.Fatalf("%s: expected Statement: sqlparser.Select, got %T", test.sql, m.Statement)
				} else {
					t.Logf("%+v\n", stmt)
				}
			}
		})
	}
}

func TestOrderBy(t *testing.T) {
	tests := []struct {
		sql string
	}{
		{
			sql: "select distinct score from grouper order by score asc",
		},
		{
			sql: "select distinct score from grouper order by score desc",
		},
		{
			sql: "select distinct score from grouper order by score asc limit 5",
		},
		{
			sql: "select distinct score from grouper order by score desc limit 5",
		},
	}
	mapper := NewMapper()
	for i, test := range tests {
		t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
			if m, err := mapper.MapSQL(test.sql); err != nil {
				t.Fatal(err)
			} else {
				if stmt, ok := m.Statement.(*sqlparser.Select); !ok {
					t.Fatalf("%s: expected Statement: sqlparser.Select, got %T", test.sql, m.Statement)
				} else {
					t.Logf("%+v\n", stmt)
				}
			}
		})
	}
}
