package clause_test

import (
	"database/sql"
	"fmt"
	"reflect"
	"sync"
	"testing"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"
	"gorm.io/gorm/utils/tests"
)

func TestExpr(t *testing.T) {
	results := []struct {
		SQL    string
		Result string
		Vars   []interface{}
	}{{
		SQL:    "create table ? (? ?, ? ?)",
		Vars:   []interface{}{clause.Table{Name: "users"}, clause.Column{Name: "id"}, clause.Expr{SQL: "int"}, clause.Column{Name: "name"}, clause.Expr{SQL: "text"}},
		Result: "create table `users` (`id` int, `name` text)",
	}}

	for idx, result := range results {
		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) {
			user, _ := schema.Parse(&tests.User{}, &sync.Map{}, db.NamingStrategy)
			stmt := &gorm.Statement{DB: db, Table: user.Table, Schema: user, Clauses: map[string]clause.Clause{}}
			clause.Expr{SQL: result.SQL, Vars: result.Vars}.Build(stmt)
			if stmt.SQL.String() != result.Result {
				t.Errorf("generated SQL is not equal, expects %v, but got %v", result.Result, stmt.SQL.String())
			}
		})
	}
}

func TestNamedExpr(t *testing.T) {
	type Base struct {
		Name2 string
	}

	type NamedArgument struct {
		Name1 string
		Base
	}

	results := []struct {
		SQL          string
		Result       string
		Vars         []interface{}
		ExpectedVars []interface{}
	}{{
		SQL:    "create table ? (? ?, ? ?)",
		Vars:   []interface{}{clause.Table{Name: "users"}, clause.Column{Name: "id"}, clause.Expr{SQL: "int"}, clause.Column{Name: "name"}, clause.Expr{SQL: "text"}},
		Result: "create table `users` (`id` int, `name` text)",
	}, {
		SQL:          "name1 = @name AND name2 = @name",
		Vars:         []interface{}{sql.Named("name", "jinzhu")},
		Result:       "name1 = ? AND name2 = ?",
		ExpectedVars: []interface{}{"jinzhu", "jinzhu"},
	}, {
		SQL:          "name1 = @name AND name2 = @@name",
		Vars:         []interface{}{map[string]interface{}{"name": "jinzhu"}},
		Result:       "name1 = ? AND name2 = @@name",
		ExpectedVars: []interface{}{"jinzhu"},
	}, {
		SQL:          "name1 = @name1 AND name2 = @name2 AND name3 = @name1",
		Vars:         []interface{}{sql.Named("name1", "jinzhu"), sql.Named("name2", "jinzhu2")},
		Result:       "name1 = ? AND name2 = ? AND name3 = ?",
		ExpectedVars: []interface{}{"jinzhu", "jinzhu2", "jinzhu"},
	}, {
		SQL:          "name1 = @name1 AND name2 = @name2 AND name3 = @name1",
		Vars:         []interface{}{map[string]interface{}{"name1": "jinzhu", "name2": "jinzhu2"}},
		Result:       "name1 = ? AND name2 = ? AND name3 = ?",
		ExpectedVars: []interface{}{"jinzhu", "jinzhu2", "jinzhu"},
	}, {
		SQL:          "@@test AND name1 = @name1 AND name2 = @name2 AND name3 = @name1 @notexist",
		Vars:         []interface{}{sql.Named("name1", "jinzhu"), sql.Named("name2", "jinzhu2")},
		Result:       "@@test AND name1 = ? AND name2 = ? AND name3 = ? @notexist",
		ExpectedVars: []interface{}{"jinzhu", "jinzhu2", "jinzhu"},
	}, {
		SQL:          "@@test AND name1 = @Name1 AND name2 = @Name2 AND name3 = @Name1 @notexist",
		Vars:         []interface{}{NamedArgument{Name1: "jinzhu", Base: Base{Name2: "jinzhu2"}}},
		Result:       "@@test AND name1 = ? AND name2 = ? AND name3 = ? @notexist",
		ExpectedVars: []interface{}{"jinzhu", "jinzhu2", "jinzhu"},
	}, {
		SQL:    "create table ? (? ?, ? ?)",
		Vars:   []interface{}{},
		Result: "create table ? (? ?, ? ?)",
	}, {
		SQL:          "name1 = @name AND name2 = @name;",
		Vars:         []interface{}{sql.Named("name", "jinzhu")},
		Result:       "name1 = ? AND name2 = ?;",
		ExpectedVars: []interface{}{"jinzhu", "jinzhu"},
	}, {
		SQL:          "name1 = @name1\r\n AND name2 = @name2",
		Vars:         []interface{}{map[string]interface{}{"name1": "jinzhu", "name2": "jinzhu"}},
		Result:       "name1 = ?\r\n AND name2 = ?",
		ExpectedVars: []interface{}{"jinzhu", "jinzhu"},
	}, {
		SQL:          "name1 = @name1\r AND name2 = @name2",
		Vars:         []interface{}{map[string]interface{}{"name1": "jinzhu", "name2": "jinzhu"}},
		Result:       "name1 = ?\r AND name2 = ?",
		ExpectedVars: []interface{}{"jinzhu", "jinzhu"},
	}, {
		SQL:    "?",
		Vars:   []interface{}{clause.Column{Table: "table", Name: "col"}},
		Result: "`table`.`col`",
	}, {
		SQL:    "?",
		Vars:   []interface{}{clause.Column{Table: "table", Name: "col", Raw: true}},
		Result: "table.col",
	}, {
		SQL:    "?",
		Vars:   []interface{}{clause.Column{Table: "table", Name: clause.PrimaryKey, Raw: true}},
		Result: "table.id",
	}, {
		SQL:    "?",
		Vars:   []interface{}{clause.Column{Table: "table", Name: "col", Alias: "alias"}},
		Result: "`table`.`col` AS `alias`",
	}, {
		SQL:    "?",
		Vars:   []interface{}{clause.Column{Table: "table", Name: "col", Alias: "alias", Raw: true}},
		Result: "table.col AS alias",
	}, {
		SQL:    "?",
		Vars:   []interface{}{clause.Table{Name: "table", Alias: "alias"}},
		Result: "`table` `alias`",
	}, {
		SQL:    "?",
		Vars:   []interface{}{clause.Table{Name: "table", Alias: "alias", Raw: true}},
		Result: "table alias",
	}}

	for idx, result := range results {
		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) {
			user, _ := schema.Parse(&tests.User{}, &sync.Map{}, db.NamingStrategy)
			stmt := &gorm.Statement{DB: db, Table: user.Table, Schema: user, Clauses: map[string]clause.Clause{}}
			clause.NamedExpr{SQL: result.SQL, Vars: result.Vars}.Build(stmt)
			if stmt.SQL.String() != result.Result {
				t.Errorf("generated SQL is not equal, expects %v, but got %v", result.Result, stmt.SQL.String())
			}

			if !reflect.DeepEqual(result.ExpectedVars, stmt.Vars) {
				t.Errorf("generated vars is not equal, expects %v, but got %v", result.ExpectedVars, stmt.Vars)
			}
		})
	}
}

func TestExpression(t *testing.T) {
	column := "column-name"
	results := []struct {
		Expressions  []clause.Expression
		ExpectedVars []interface{}
		Result       string
	}{{
		Expressions: []clause.Expression{
			clause.Eq{Column: column, Value: "column-value"},
		},
		ExpectedVars: []interface{}{"column-value"},
		Result:       "`column-name` = ?",
	}, {
		Expressions: []clause.Expression{
			clause.Eq{Column: column, Value: nil},
			clause.Eq{Column: column, Value: (*string)(nil)},
			clause.Eq{Column: column, Value: (*int)(nil)},
			clause.Eq{Column: column, Value: (*bool)(nil)},
			clause.Eq{Column: column, Value: (interface{})(nil)},
			clause.Eq{Column: column, Value: sql.NullString{String: "", Valid: false}},
		},
		Result: "`column-name` IS NULL",
	}, {
		Expressions: []clause.Expression{
			clause.Neq{Column: column, Value: "column-value"},
		},
		ExpectedVars: []interface{}{"column-value"},
		Result:       "`column-name` <> ?",
	}, {
		Expressions: []clause.Expression{
			clause.Neq{Column: column, Value: nil},
			clause.Neq{Column: column, Value: (*string)(nil)},
			clause.Neq{Column: column, Value: (*int)(nil)},
			clause.Neq{Column: column, Value: (*bool)(nil)},
			clause.Neq{Column: column, Value: (interface{})(nil)},
		},
		Result: "`column-name` IS NOT NULL",
	}, {
		Expressions: []clause.Expression{
			clause.Eq{Column: column, Value: []string{"a", "b"}},
		},
		ExpectedVars: []interface{}{"a", "b"},
		Result:       "`column-name` IN (?,?)",
	}, {
		Expressions: []clause.Expression{
			clause.Neq{Column: column, Value: []string{"a", "b"}},
		},
		ExpectedVars: []interface{}{"a", "b"},
		Result:       "`column-name` NOT IN (?,?)",
	}, {
		Expressions: []clause.Expression{
			clause.Eq{Column: column, Value: []string{}},
		},
		Result: "`column-name` IN (NULL)",
	}, {
		Expressions: []clause.Expression{
			clause.Eq{Column: clause.Expr{SQL: "SUM(?)", Vars: []interface{}{clause.Column{Name: "id"}}}, Value: 100},
		},
		ExpectedVars: []interface{}{100},
		Result:       "SUM(`id`) = ?",
	}, {
		Expressions: []clause.Expression{
			clause.Gte{Column: clause.Expr{SQL: "SUM(?)", Vars: []interface{}{clause.Column{Table: "users", Name: "id"}}}, Value: 100},
		},
		ExpectedVars: []interface{}{100},
		Result:       "SUM(`users`.`id`) >= ?",
	}}

	for idx, result := range results {
		for idy, expression := range result.Expressions {
			t.Run(fmt.Sprintf("case #%v.%v", idx, idy), func(t *testing.T) {
				user, _ := schema.Parse(&tests.User{}, &sync.Map{}, db.NamingStrategy)
				stmt := &gorm.Statement{DB: db, Table: user.Table, Schema: user, Clauses: map[string]clause.Clause{}}
				expression.Build(stmt)
				if stmt.SQL.String() != result.Result {
					t.Errorf("generated SQL is not equal, expects %v, but got %v", result.Result, stmt.SQL.String())
				}

				if !reflect.DeepEqual(result.ExpectedVars, stmt.Vars) {
					t.Errorf("generated vars is not equal, expects %v, but got %v", result.ExpectedVars, stmt.Vars)
				}
			})
		}
	}
}
