// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.

package sqlbuilder

import (
	"fmt"
	"testing"

	"github.com/huandu/go-assert"
)

func ExampleDeleteFrom() {
	sql := DeleteFrom("demo.user").
		Where(
			"status = 1",
		).
		Limit(10).
		String()

	fmt.Println(sql)

	// Output:
	// DELETE FROM demo.user WHERE status = 1 LIMIT ?
}

func ExampleDeleteBuilder() {
	db := NewDeleteBuilder()
	db.DeleteFrom("demo.user")
	db.Where(
		db.GreaterThan("id", 1234),
		db.Like("name", "%Du"),
		db.Or(
			db.IsNull("id_card"),
			db.In("status", 1, 2, 5),
		),
		"modified_at > created_at + "+db.Var(86400), // It's allowed to write arbitrary SQL.
	)

	sql, args := db.Build()
	fmt.Println(sql)
	fmt.Println(args)

	// Output:
	// DELETE FROM demo.user WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ?
	// [1234 %Du 1 2 5 86400]
}

func ExampleDeleteBuilder_SQL() {
	db := NewDeleteBuilder()
	db.SQL(`/* before */`)
	db.DeleteFrom("demo.user")
	db.SQL("PARTITION (p0)")
	db.Where(
		db.GreaterThan("id", 1234),
	)
	db.SQL("/* after where */")
	db.OrderBy("id")
	db.SQL("/* after order by */")
	db.Limit(10)
	db.SQL("/* after limit */")

	sql, args := db.Build()
	fmt.Println(sql)
	fmt.Println(args)

	// Output:
	// /* before */ DELETE FROM demo.user PARTITION (p0) WHERE id > ? /* after where */ ORDER BY id /* after order by */ LIMIT ? /* after limit */
	// [1234 10]
}

func ExampleDeleteBuilder_With() {
	sql := With(
		CTEQuery("users").As(
			Select("id", "name").From("users").Where("name IS NULL"),
		),
	).DeleteFrom("orders").Where(
		"users.id = orders.user_id",
	).String()

	fmt.Println(sql)

	// Output:
	// WITH users AS (SELECT id, name FROM users WHERE name IS NULL) DELETE FROM orders WHERE users.id = orders.user_id
}

func TestDeleteBuilderGetFlavor(t *testing.T) {
	a := assert.New(t)
	db := newDeleteBuilder()

	db.SetFlavor(PostgreSQL)
	flavor := db.Flavor()
	a.Equal(PostgreSQL, flavor)

	dbClick := ClickHouse.NewDeleteBuilder()
	flavor = dbClick.Flavor()
	a.Equal(ClickHouse, flavor)
}

func ExampleDeleteBuilder_Returning() {
	db := NewDeleteBuilder()
	db.DeleteFrom("user")
	db.Where(db.Equal("id", 123))
	db.Returning("id", "deleted_at")

	sql, args := db.BuildWithFlavor(PostgreSQL)
	fmt.Println(sql)
	fmt.Println(args)

	// Output:
	// DELETE FROM user WHERE id = $1 RETURNING id, deleted_at
	// [123]
}

func TestDeleteBuilderReturning(t *testing.T) {
	a := assert.New(t)
	db := NewDeleteBuilder()
	db.DeleteFrom("user")
	db.Where(db.Equal("id", 123))
	db.Returning("id", "deleted_at")

	sql, _ := db.BuildWithFlavor(MySQL)
	a.Equal("DELETE FROM user WHERE id = ?", sql)

	sql, _ = db.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM user WHERE id = $1 RETURNING id, deleted_at", sql)

	sql, _ = db.BuildWithFlavor(SQLite)
	a.Equal("DELETE FROM user WHERE id = ? RETURNING id, deleted_at", sql)

	sql, _ = db.BuildWithFlavor(SQLServer)
	a.Equal("DELETE FROM user OUTPUT DELETED.id, DELETED.deleted_at WHERE id = @p1", sql)

	sql, _ = db.BuildWithFlavor(CQL)
	a.Equal("DELETE FROM user WHERE id = ?", sql)

	sql, _ = db.BuildWithFlavor(ClickHouse)
	a.Equal("DELETE FROM user WHERE id = ?", sql)

	sql, _ = db.BuildWithFlavor(Presto)
	a.Equal("DELETE FROM user WHERE id = ?", sql)

	// Test with no returning columns
	db2 := NewDeleteBuilder()
	db2.DeleteFrom("user")
	db2.Where(db2.Equal("id", 1))
	db2.Returning() // Empty returning

	sql, _ = db2.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM user WHERE id = $1", sql)

	// Test with single column
	db3 := NewDeleteBuilder()
	db3.DeleteFrom("user")
	db3.Where(db3.Equal("id", 1))
	db3.Returning("id")

	sql, _ = db3.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM user WHERE id = $1 RETURNING id", sql)

	// Test with ORDER BY and LIMIT
	db4 := NewDeleteBuilder()
	db4.DeleteFrom("user")
	db4.Where(db4.Equal("status", 1))
	db4.OrderBy("id").Asc()
	db4.Limit(5)
	db4.Returning("id", "name")

	sql, _ = db4.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM user WHERE status = $1 ORDER BY id ASC LIMIT $2 RETURNING id, name", sql)

	// Test chaining
	db5 := NewDeleteBuilder().DeleteFrom("user").Where("status = 0").Returning("id").Returning("name", "deleted_at")
	sql, _ = db5.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM user WHERE status = 0 RETURNING name, deleted_at", sql) // Last Returning call overwrites

	// Test SQL injection after RETURNING
	db6 := NewDeleteBuilder()
	db6.DeleteFrom("user")
	db6.Where(db6.Equal("id", 1))
	db6.Returning("id", "name")
	db6.SQL("/* comment after returning */")

	sql, _ = db6.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM user WHERE id = $1 RETURNING id, name /* comment after returning */", sql)

	// Test with CTE (WITH clause)
	cte := With(CTETable("temp_user").As(Select("id").From("inactive_users")))
	db7 := cte.DeleteFrom("user")
	db7.Where("user.id IN (SELECT id FROM temp_user)")
	db7.Returning("id", "deleted_at")

	sql, _ = db7.BuildWithFlavor(PostgreSQL)
	a.Equal("WITH temp_user AS (SELECT id FROM inactive_users) DELETE FROM user, temp_user WHERE user.id IN (SELECT id FROM temp_user) RETURNING id, deleted_at", sql)
}

func ExampleDeleteBuilder_Using() {
	db := NewDeleteBuilder()
	db.DeleteFrom("orders")
	db.Using("customers")
	db.Where(
		"orders.customer_id = customers.id",
		db.Equal("customers.status", "inactive"),
	)

	sql, args := db.BuildWithFlavor(PostgreSQL)
	fmt.Println(sql)
	fmt.Println(args)

	// Output:
	// DELETE FROM orders USING customers WHERE orders.customer_id = customers.id AND customers.status = $1
	// [inactive]
}

func TestDeleteBuilderUsing(t *testing.T) {
	a := assert.New(t)

	// Single USING table
	db := NewDeleteBuilder()
	db.DeleteFrom("orders")
	db.Using("customers")
	db.Where("orders.customer_id = customers.id")

	sql, _ := db.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM orders USING customers WHERE orders.customer_id = customers.id", sql)

	// Multiple USING tables
	db2 := NewDeleteBuilder()
	db2.DeleteFrom("orders")
	db2.Using("customers", "products")
	db2.Where("orders.customer_id = customers.id", "orders.product_id = products.id")

	sql, _ = db2.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM orders USING customers, products WHERE orders.customer_id = customers.id AND orders.product_id = products.id", sql)

	// USING with RETURNING
	db3 := NewDeleteBuilder()
	db3.DeleteFrom("orders")
	db3.Using("customers")
	db3.Where(db3.Equal("customers.id", 42))
	db3.Returning("orders.id")

	sql, args := db3.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM orders USING customers WHERE customers.id = $1 RETURNING orders.id", sql)
	a.Equal([]interface{}{42}, args)

	// SQL injection after USING
	db4 := NewDeleteBuilder()
	db4.DeleteFrom("orders")
	db4.Using("customers")
	db4.SQL("/* after using */")
	db4.Where("orders.customer_id = customers.id")

	sql, _ = db4.BuildWithFlavor(PostgreSQL)
	a.Equal("DELETE FROM orders USING customers /* after using */ WHERE orders.customer_id = customers.id", sql)
}

func TestDeleteBuilderClone(t *testing.T) {
	a := assert.New(t)
	cte := With(
		CTETable("temp").As(Select("id").From("to_delete")),
	)
	db := cte.DeleteFrom("target").Where("temp.id = target.id").OrderBy("id").Asc().Limit(3).Returning("id")

	clone := db.Clone()
	s1, args1 := db.BuildWithFlavor(PostgreSQL)
	s2, args2 := clone.BuildWithFlavor(PostgreSQL)
	a.Equal(s1, s2)
	a.Equal(args1, args2)

	clone.Desc().Limit(5)
	a.NotEqual(db.String(), clone.String())
}
