package model

import (
	"context"
	"database/sql"
	"fmt"
	"github.com/fthvgb1/wp-go/helper/number"
	"github.com/fthvgb1/wp-go/helper/slice"
	"reflect"
	"strconv"
	"strings"
	"testing"
)

func TestFinds(t *testing.T) {
	type args struct {
		ctx context.Context
		q   *QueryCondition
	}
	type testCase[T Model] struct {
		name    string
		args    args
		wantR   []T
		wantErr bool
	}
	tests := []testCase[post]{
		{
			name: "t1",
			args: args{
				ctx: context.Background(),
				q: Conditions(
					Where(SqlBuilder{
						{"post_status", "publish"}, {"ID", "in", ""}},
					),
					Order(SqlBuilder{{"ID", "desc"}}),
					Offset(10),
					Limit(10),
					In([][]any{slice.ToAnySlice(number.Range(1, 1000, 1))}...),
				),
			},
			wantR: func() []post {
				r, err := Select[post](ctx, "select * from "+post{}.Table()+" where post_status='publish' and ID in ("+strings.Join(slice.Map(number.Range(1, 1000, 1), strconv.Itoa), ",")+") order by ID desc limit 10 offset 10 ")
				if err != nil {
					panic(err)
				}
				return r
			}(),
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotR, err := Finds[post](tt.args.ctx, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("Findx() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, tt.wantR) {
				t.Errorf("Findx() gotR = %v, want %v", gotR, tt.wantR)
			}
		})
	}
}

func TestChunkFind(t *testing.T) {
	type args struct {
		ctx      context.Context
		perLimit int
		q        *QueryCondition
	}
	type testCase[T Model] struct {
		name    string
		args    args
		wantR   []T
		wantErr bool
	}
	n := 500
	tests := []testCase[post]{
		{
			name: "in,orderBy",
			args: args{
				ctx: ctx,
				q: Conditions(
					Where(SqlBuilder{{
						"post_status", "publish",
					}, {"ID", "in", ""}}),
					Order(SqlBuilder{{"ID", "desc"}}),
					In([][]any{slice.ToAnySlice(number.Range(1, n, 1))}...),
				),
				perLimit: 20,
			},
			wantR: func() []post {
				r, err := Select[post](ctx, "select * from "+post{}.Table()+" where post_status='publish' and ID in ("+strings.Join(slice.Map(number.Range(1, n, 1), strconv.Itoa), ",")+")  order by ID desc")
				if err != nil {
					panic(err)
				}
				return r
			}(),
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotR, err := ChunkFind[post](tt.args.ctx, tt.args.perLimit, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("ChunkFind() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, tt.wantR) {
				t.Errorf("ChunkFind() gotR = %v, want %v", gotR, tt.wantR)
			}
		})
	}
}

func TestChunk(t *testing.T) {
	type args[T Model, R any] struct {
		ctx      context.Context
		perLimit int
		fn       func(rows T) (R, bool)
		q        *QueryCondition
	}
	type testCase[T Model, R any] struct {
		name    string
		args    args[T, R]
		wantR   []R
		wantErr bool
	}
	n := 500
	tests := []testCase[post, uint64]{
		{
			name: "t1",
			args: args[post, uint64]{
				ctx:      ctx,
				perLimit: 20,
				fn: func(t post) (uint64, bool) {
					if t.Id > 300 {
						return t.Id, true
					}
					return 0, false
				},
				q: Conditions(
					Where(SqlBuilder{{
						"post_status", "publish",
					}, {"ID", "in", ""}}),
					Order(SqlBuilder{{"ID", "desc"}}),
					In([][]any{slice.ToAnySlice(number.Range(1, n, 1))}...),
				),
			},
			wantR: func() []uint64 {
				r, err := Select[post](ctx, "select * from "+post{}.Table()+" where post_status='publish' and ID in ("+strings.Join(slice.Map(number.Range(1, n, 1), strconv.Itoa), ",")+")  order by ID desc")
				if err != nil {
					panic(err)
				}
				return slice.FilterAndMap(r, func(t post) (uint64, bool) {
					if t.Id <= 300 {
						return 0, false
					}
					return t.Id, true
				})
			}(),
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotR, err := Chunk[post](tt.args.ctx, tt.args.perLimit, tt.args.fn, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("Chunk() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, tt.wantR) {
				t.Errorf("Chunk() gotR = %v, want %v", gotR, tt.wantR)
			}
		})
	}
}

func TestPagination(t *testing.T) {
	type args struct {
		ctx      context.Context
		q        *QueryCondition
		page     int
		pageSize int
	}
	type testCase[T Model] struct {
		name    string
		args    args
		want    []T
		want1   int
		wantErr bool
	}
	tests := []testCase[post]{
		{
			name: "t1",
			args: args{
				ctx: ctx,
				q: Conditions(
					Where(SqlBuilder{
						{"ID", "in", ""},
					}),
					In([][]any{slice.ToAnySlice(number.Range(431, 440, 1))}...),
				),
				page:     1,
				pageSize: 5,
			},
			want: func() (r []post) {
				r, err := Select[post](ctx, "select * from "+post{}.Table()+" where ID in (?,?,?,?,?)", slice.ToAnySlice(number.Range(431, 435, 1))...)
				if err != nil && err != sql.ErrNoRows {
					panic(err)
				} else if err == sql.ErrNoRows {
					err = nil
				}
				return
			}(),
			want1:   10,
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, got1, err := Pagination[post](tt.args.ctx, tt.args.q, tt.args.page, tt.args.pageSize)
			if (err != nil) != tt.wantErr {
				t.Errorf("Pagination() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Pagination() got = %v, want %v", got, tt.want)
			}
			if got1 != tt.want1 {
				t.Errorf("Pagination() got1 = %v, want %v", got1, tt.want1)
			}
		})
	}
}

func TestColumn(t *testing.T) {
	type args[V Model, T any] struct {
		ctx context.Context
		fn  func(V) (T, bool)
		q   *QueryCondition
	}
	type testCase[V Model, T any] struct {
		name    string
		args    args[V, T]
		wantR   []T
		wantErr bool
	}
	tests := []testCase[post, uint64]{
		{
			name: "t1",
			args: args[post, uint64]{
				ctx: ctx,
				fn: func(t post) (uint64, bool) {
					return t.Id, true
				},
				q: Conditions(
					Where(SqlBuilder{
						{"ID", "<", "200", "int"},
					}),
				),
			},
			wantR: []uint64{63, 64, 190, 193},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotR, err := Column[post](tt.args.ctx, tt.args.fn, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("Column() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, tt.wantR) {
				t.Errorf("Column() gotR = %v, want %v", gotR, tt.wantR)
			}
		})
	}
}

type options struct {
	OptionId    uint64 `gorm:"column:option_id" db:"option_id" json:"option_id" form:"option_id"`
	OptionName  string `gorm:"column:option_name" db:"option_name" json:"option_name" form:"option_name"`
	OptionValue string `gorm:"column:option_value" db:"option_value" json:"option_value" form:"option_value"`
	Autoload    string `gorm:"column:autoload" db:"autoload" json:"autoload" form:"autoload"`
}

func (w options) PrimaryKey() string {
	return "option_id"
}

func (w options) Table() string {
	return "wp_options"
}

func Test_getField(t *testing.T) {
	{
		name := "string"
		db := glob
		field := "option_value"
		q := Conditions(Where(SqlBuilder{{"option_name", "blogname"}}))
		wantR := "记录并见证自己的成长"
		wantErr := false
		t.Run(name, func(t *testing.T) {
			gotR, err := getField[options](db, ctx, field, q)
			if (err != nil) != wantErr {
				t.Errorf("getField() error = %v, wantErr %v", err, wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, wantR) {
				t.Errorf("getField() gotR = %v, want %v", gotR, wantR)
			}
		})
	}

	{
		name := "t2"
		db := glob
		field := "option_id"
		q := Conditions(Where(SqlBuilder{{"option_name", "blogname"}}))
		wantR := "3"
		wantErr := false
		t.Run(name, func(t *testing.T) {
			gotR, err := getField[options](db, ctx, field, q)
			if (err != nil) != wantErr {
				t.Errorf("getField() error = %v, wantErr %v", err, wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, wantR) {
				t.Errorf("getField() gotR = %v, want %v", gotR, wantR)
			}
		})
	}
	{
		name := "count(*)"
		db := glob
		field := "count(*)"
		q := Conditions()
		wantR := "406"
		wantErr := false
		t.Run(name, func(t *testing.T) {
			gotR, err := getField[options](db, ctx, field, q)
			if (err != nil) != wantErr {
				t.Errorf("getField() error = %v, wantErr %v", err, wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, wantR) {
				t.Errorf("getField() gotR = %v, want %v", gotR, wantR)
			}
		})
	}
}

func Test_getToStringMap(t *testing.T) {
	type args struct {
		db  dbQuery
		ctx context.Context
		q   *QueryCondition
	}
	tests := []struct {
		name    string
		args    args
		wantR   map[string]string
		wantErr bool
	}{
		{
			name: "t1",
			args: args{
				db:  glob,
				ctx: ctx,
				q:   Conditions(Where(SqlBuilder{{"option_name", "users_can_register"}})),
			},
			wantR: map[string]string{
				"option_id":    "5",
				"option_value": "0",
				"option_name":  "users_can_register",
				"autoload":     "yes",
			},
		},
		{
			name: "t2",
			args: args{
				db:  glob,
				ctx: ctx,
				q: Conditions(
					Where(SqlBuilder{{"option_name", "users_can_register"}}),
					Fields("option_id id"),
				),
			},
			wantR: map[string]string{
				"id": "5",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotR, err := getToStringMap[options](tt.args.db, tt.args.ctx, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("getToStringMap() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, tt.wantR) {
				t.Errorf("getToStringMap() gotR = %v, want %v", gotR, tt.wantR)
			}
		})
	}
}

func Test_findToStringMap(t *testing.T) {
	type args struct {
		db  dbQuery
		ctx context.Context
		q   *QueryCondition
	}
	tests := []struct {
		name    string
		args    args
		wantR   []map[string]string
		wantErr bool
	}{
		{
			name: "t1",
			args: args{
				db:  glob,
				ctx: ctx,
				q:   Conditions(Where(SqlBuilder{{"option_id", "5"}})),
			},
			wantR: []map[string]string{{
				"option_id":    "5",
				"option_value": "0",
				"option_name":  "users_can_register",
				"autoload":     "yes",
			}},
			wantErr: false,
		},
		{
			name: "t2",
			args: args{
				db:  glob,
				ctx: ctx,
				q: Conditions(
					Where(SqlBuilder{{"option_id", "5"}}),
					Fields("option_value,option_name"),
				),
			},
			wantR: []map[string]string{{
				"option_value": "0",
				"option_name":  "users_can_register",
			}},
			wantErr: false,
		},
		{
			name: "t3",
			args: args{
				db:  glob,
				ctx: ctx,
				q: Conditions(
					Where(SqlBuilder{{"option_id", "5"}}),
					Fields("option_value v,option_name k"),
				),
			},
			wantR: []map[string]string{{
				"v": "0",
				"k": "users_can_register",
			}},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotR, err := findToStringMap[options](tt.args.db, tt.args.ctx, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("findToStringMap() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, tt.wantR) {
				t.Errorf("findToStringMap() gotR = %v, want %v", gotR, tt.wantR)
			}
		})
	}
}

func Test_findScanner(t *testing.T) {
	type args[T Model] struct {
		db  dbQuery
		ctx context.Context
		fn  func(T)
		q   *QueryCondition
	}
	type testCase[T Model] struct {
		name    string
		args    args[T]
		wantErr bool
	}
	tests := []testCase[options]{
		{
			name: "t1",
			args: args[options]{glob, ctx, func(t options) {
				fmt.Println(t)
			}, Conditions(Where(SqlBuilder{{"option_id", "<", "10", "int"}}))},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := findScanner[options](tt.args.db, tt.args.ctx, tt.args.fn, tt.args.q); (err != nil) != tt.wantErr {
				t.Errorf("findScanner() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func BenchmarkSqlxQueryXX(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var r []options
		err := ddb.Select(&r, "select * from wp_options where option_id<100 and option_id>50")
		if err != nil {
			panic(err)
		}
	}
}

func BenchmarkScannerXX(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var r []options
		err := findScanner[options](glob, ctx, func(t options) {
			r = append(r, t)
			//fmt.Println(t)
		}, Conditions(Where(SqlBuilder{{"option_id<100"}, {"option_id>50"}})))
		if err != nil {
			panic(err)
		}
	}
}

func BenchmarkFindsXX(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_, err := finds[options](glob, ctx, Conditions(
			Where(SqlBuilder{{"option_id<100"}, {"option_id>50"}})),
		)
		if err != nil {
			panic(err)
		}
	}
}

func Test_gets(t *testing.T) {
	type args struct {
		db  dbQuery
		ctx context.Context
		q   *QueryCondition
	}
	type testCase[T Model] struct {
		name    string
		args    args
		wantR   T
		wantErr bool
	}
	tests := []testCase[options]{
		{
			name: "t1",
			args: args{
				db:  glob,
				ctx: ctx,
				q:   Conditions(Where(SqlBuilder{{"option_name", "blogname"}})),
			},
			wantR:   options{3, "blogname", "记录并见证自己的成长", "yes"},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotR, err := gets[options](tt.args.db, tt.args.ctx, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("gets() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, tt.wantR) {
				t.Errorf("gets() gotR = %v, want %v", gotR, tt.wantR)
			}
		})
	}
}

func Test_finds(t *testing.T) {
	type args struct {
		db  dbQuery
		ctx context.Context
		q   *QueryCondition
	}
	type testCase[T Model] struct {
		name    string
		args    args
		wantR   []T
		wantErr bool
	}
	var u user
	tests := []testCase[options]{
		{
			name: "sub query",
			args: args{db: glob, ctx: ctx, q: Conditions(
				From("(select * from wp_options where option_id <100) a"),
				Where(SqlBuilder{{"option_id", ">", "50", "int"}}),
			)},
			wantR: func() []options {
				r, err := Select[options](ctx, "select * from (select * from wp_options where option_id <100) a where option_id>50")
				if err != nil {
					panic(err)
				}
				return r
			}(),
			wantErr: false,
		},
		{
			name: "mixed query",
			args: args{db: glob, ctx: ctx, q: Conditions(
				From("(select * from wp_options where option_id <100) a"),
				Where(SqlBuilder{
					{"u.ID", "<", "50", "int"}}),
				Join(SqlBuilder{
					{"left join", user.Table(u) + " u", "a.option_id=u.ID"},
				}),
				Fields("u.user_login autoload,option_name,option_value"),
			)},
			wantR: func() []options {
				r, err := Select[options](ctx, "select u.user_login autoload,option_name,option_value from (select * from wp_options where option_id <100) a  left join  wp_users u  on a.option_id=u.ID   where `u`.`ID`<50")
				if err != nil {
					panic(err)
				}
				return r
			}(),
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotR, err := finds[options](tt.args.db, tt.args.ctx, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("finds() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotR, tt.wantR) {
				t.Errorf("finds() gotR = %v, want %v", gotR, tt.wantR)
			}
		})
	}
}

func TestGets(t *testing.T) {
	type args struct {
		ctx context.Context
		q   *QueryCondition
	}
	type testCase[T Model] struct {
		name    string
		args    args
		want    T
		wantErr bool
	}
	tests := []testCase[options]{
		{
			name: "t1",
			args: args{
				ctx: ctx,
				q: Conditions(Where(SqlBuilder{
					{"option_id", "11"},
				})),
			},
			want: options{
				OptionId:    11,
				OptionName:  "comments_notify",
				OptionValue: "1",
				Autoload:    "yes",
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := Gets[options](tt.args.ctx, tt.args.q)
			gots, _ := gets[options](glob, tt.args.ctx, tt.args.q)
			gotss, _ := GetsFromDB[options](glob, tt.args.ctx, tt.args.q)
			if (err != nil) != tt.wantErr {
				t.Errorf("Gets() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, gots) {
				t.Errorf("Gets() got = %v, want %v", got, tt.want)
			}
			if !reflect.DeepEqual(got, gotss) {
				t.Errorf("Gets() got = %v, want %v", got, tt.want)
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Gets() got = %v, want %v", got, tt.want)
			}
		})
	}
}
