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

package sqlbuilder

import (
	"fmt"
	"reflect"

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

const (
	updateMarkerInit injectionMarker = iota
	updateMarkerAfterWith
	updateMarkerAfterUpdate
	updateMarkerAfterSet
	updateMarkerAfterFrom
	updateMarkerAfterWhere
	updateMarkerAfterOrderBy
	updateMarkerAfterLimit
	updateMarkerAfterReturning
)

// NewUpdateBuilder creates a new UPDATE builder.
func NewUpdateBuilder() *UpdateBuilder {
	return DefaultFlavor.NewUpdateBuilder()
}

func newUpdateBuilder() *UpdateBuilder {
	args := &Args{}
	proxy := &whereClauseProxy{}
	return &UpdateBuilder{
		whereClauseProxy: proxy,
		whereClauseExpr:  args.Add(proxy),

		Cond: Cond{
			Args: args,
		},
		args:      args,
		injection: newInjection(),
	}
}

// Clone returns a deep copy of UpdateBuilder.
// It's useful when you want to create a base builder and clone it to build similar queries.
func (ub *UpdateBuilder) Clone() *UpdateBuilder {
	return clone.Clone(ub).(*UpdateBuilder)
}

func init() {
	t := reflect.TypeOf(UpdateBuilder{})
	clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
		cloned := allocator.CloneSlowly(old)
		new.Set(cloned)

		ub := cloned.Addr().Interface().(*UpdateBuilder)
		ub.args.Replace(ub.whereClauseExpr, ub.whereClauseProxy)
		ub.args.Replace(ub.cteBuilderVar, ub.cteBuilder)
	})
}

// UpdateBuilder is a builder to build UPDATE.
type UpdateBuilder struct {
	*WhereClause
	Cond

	whereClauseProxy *whereClauseProxy
	whereClauseExpr  string

	cteBuilderVar string
	cteBuilder    *CTEBuilder

	tables      []string
	fromTables  []string
	assignments []string
	orderByCols []string
	order       string
	limitVar    string
	returning   []string

	args *Args

	injection *injection
	marker    injectionMarker
}

var _ Builder = new(UpdateBuilder)

// Update sets table name in UPDATE.
func Update(table ...string) *UpdateBuilder {
	return DefaultFlavor.NewUpdateBuilder().Update(table...)
}

// With sets WITH clause (the Common Table Expression) before UPDATE.
func (ub *UpdateBuilder) With(builder *CTEBuilder) *UpdateBuilder {
	ub.marker = updateMarkerAfterWith
	ub.cteBuilderVar = ub.Var(builder)
	ub.cteBuilder = builder
	return ub
}

// Update sets table name in UPDATE.
func (ub *UpdateBuilder) Update(table ...string) *UpdateBuilder {
	ub.tables = table
	ub.marker = updateMarkerAfterUpdate
	return ub
}

// TableNames returns all table names in this UPDATE statement.
func (ub *UpdateBuilder) TableNames() (tableNames []string) {
	var additionalTableNames []string

	if ub.cteBuilder != nil {
		additionalTableNames = ub.cteBuilder.tableNamesForFrom()
	}

	if len(ub.tables) > 0 && len(additionalTableNames) > 0 {
		tableNames = make([]string, len(ub.tables)+len(additionalTableNames))
		copy(tableNames, ub.tables)
		copy(tableNames[len(ub.tables):], additionalTableNames)
	} else if len(ub.tables) > 0 {
		tableNames = ub.tables
	} else if len(additionalTableNames) > 0 {
		tableNames = additionalTableNames
	}

	return tableNames
}

// Set sets the assignments in SET.
func (ub *UpdateBuilder) Set(assignment ...string) *UpdateBuilder {
	ub.assignments = assignment
	ub.marker = updateMarkerAfterSet
	return ub
}

// SetMore appends the assignments in SET.
func (ub *UpdateBuilder) SetMore(assignment ...string) *UpdateBuilder {
	ub.assignments = append(ub.assignments, assignment...)
	ub.marker = updateMarkerAfterSet
	return ub
}

// From sets table names of FROM in UPDATE.
func (ub *UpdateBuilder) From(table ...string) *UpdateBuilder {
	ub.fromTables = table
	ub.marker = updateMarkerAfterFrom
	return ub
}

// Where adds expressions to the WHERE clause in UPDATE.
//
// Multiple calls to Where will join expressions with AND.
// To reset the WHERE clause, set the WhereClause field to nil.
func (ub *UpdateBuilder) Where(andExpr ...string) *UpdateBuilder {
	if len(andExpr) == 0 || estimateStringsBytes(andExpr) == 0 {
		return ub
	}

	if ub.WhereClause == nil {
		ub.WhereClause = NewWhereClause()
	}

	ub.WhereClause.AddWhereExpr(ub.args, andExpr...)
	ub.marker = updateMarkerAfterWhere
	return ub
}

// AddWhereClause adds all clauses in the whereClause to SELECT.
func (ub *UpdateBuilder) AddWhereClause(whereClause *WhereClause) *UpdateBuilder {
	if ub.WhereClause == nil {
		ub.WhereClause = NewWhereClause()
	}

	ub.WhereClause.AddWhereClause(whereClause)
	return ub
}

// Assign represents SET "field = value" in UPDATE.
func (ub *UpdateBuilder) Assign(field string, value interface{}) string {
	return fmt.Sprintf("%s = %s", Escape(field), ub.args.Add(value))
}

// Incr represents SET "field = field + 1" in UPDATE.
func (ub *UpdateBuilder) Incr(field string) string {
	f := Escape(field)
	return fmt.Sprintf("%s = %s + 1", f, f)
}

// Decr represents SET "field = field - 1" in UPDATE.
func (ub *UpdateBuilder) Decr(field string) string {
	f := Escape(field)
	return fmt.Sprintf("%s = %s - 1", f, f)
}

// Add represents SET "field = field + value" in UPDATE.
func (ub *UpdateBuilder) Add(field string, value interface{}) string {
	f := Escape(field)
	return fmt.Sprintf("%s = %s + %s", f, f, ub.args.Add(value))
}

// Sub represents SET "field = field - value" in UPDATE.
func (ub *UpdateBuilder) Sub(field string, value interface{}) string {
	f := Escape(field)
	return fmt.Sprintf("%s = %s - %s", f, f, ub.args.Add(value))
}

// Mul represents SET "field = field * value" in UPDATE.
func (ub *UpdateBuilder) Mul(field string, value interface{}) string {
	f := Escape(field)
	return fmt.Sprintf("%s = %s * %s", f, f, ub.args.Add(value))
}

// Div represents SET "field = field / value" in UPDATE.
func (ub *UpdateBuilder) Div(field string, value interface{}) string {
	f := Escape(field)
	return fmt.Sprintf("%s = %s / %s", f, f, ub.args.Add(value))
}

// OrderBy sets columns of ORDER BY in UPDATE.
//
// It's recommended to use OrderByAsc or OrderByDesc instead for better support of multiple ORDER BY columns with different directions.
// OrderBy combined with Asc/Desc only supports a single direction for all columns.
func (ub *UpdateBuilder) OrderBy(col ...string) *UpdateBuilder {
	ub.orderByCols = col
	ub.marker = updateMarkerAfterOrderBy
	return ub
}

// OrderByAsc sets a column of ORDER BY in UPDATE with ASC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
//	ub.OrderByAsc("name").OrderByDesc("id")
//	// Generates: ORDER BY name ASC, id DESC
func (ub *UpdateBuilder) OrderByAsc(col string) *UpdateBuilder {
	ub.orderByCols = append(ub.orderByCols, col+" ASC")
	ub.marker = updateMarkerAfterOrderBy
	return ub
}

// OrderByDesc sets a column of ORDER BY in UPDATE with DESC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
//	ub.OrderByDesc("id").OrderByAsc("name")
//	// Generates: ORDER BY id DESC, name ASC
func (ub *UpdateBuilder) OrderByDesc(col string) *UpdateBuilder {
	ub.orderByCols = append(ub.orderByCols, col+" DESC")
	ub.marker = updateMarkerAfterOrderBy
	return ub
}

// Asc sets order of ORDER BY to ASC.
//
// Deprecated: Use OrderByAsc instead. Asc only supports a single direction for all ORDER BY columns.
func (ub *UpdateBuilder) Asc() *UpdateBuilder {
	ub.order = "ASC"
	ub.marker = updateMarkerAfterOrderBy
	return ub
}

// Desc sets order of ORDER BY to DESC.
//
// Deprecated: Use OrderByDesc instead. Desc only supports a single direction for all ORDER BY columns.
func (ub *UpdateBuilder) Desc() *UpdateBuilder {
	ub.order = "DESC"
	ub.marker = updateMarkerAfterOrderBy
	return ub
}

// Limit sets the LIMIT in UPDATE.
func (ub *UpdateBuilder) Limit(limit int) *UpdateBuilder {
	if limit < 0 {
		ub.limitVar = ""
		return ub
	}

	ub.limitVar = ub.Var(limit)
	ub.marker = updateMarkerAfterLimit
	return ub
}

// Returning sets returning columns.
// For DBMS that doesn't support RETURNING, e.g. MySQL, it will be ignored.
func (ub *UpdateBuilder) Returning(col ...string) *UpdateBuilder {
	ub.returning = col
	ub.marker = updateMarkerAfterReturning
	return ub
}

// NumAssignment returns the number of assignments to update.
func (ub *UpdateBuilder) NumAssignment() int {
	return len(ub.assignments)
}

// String returns the compiled UPDATE string.
func (ub *UpdateBuilder) String() string {
	s, _ := ub.Build()
	return s
}

// Build returns compiled UPDATE string and args.
// They can be used in `DB#Query` of package `database/sql` directly.
func (ub *UpdateBuilder) Build() (sql string, args []interface{}) {
	return ub.BuildWithFlavor(ub.args.Flavor)
}

// BuildWithFlavor returns compiled UPDATE string and args with flavor and initial args.
// They can be used in `DB#Query` of package `database/sql` directly.
func (ub *UpdateBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}) (sql string, args []interface{}) {
	buf := newStringBuilder()
	ub.injection.WriteTo(buf, updateMarkerInit)

	if ub.cteBuilder != nil {
		buf.WriteLeadingString(ub.cteBuilderVar)
		ub.injection.WriteTo(buf, updateMarkerAfterWith)
	}

	switch flavor {
	case MySQL:
		// CTE table names should be written after UPDATE keyword in MySQL.
		tableNames := ub.TableNames()

		if len(tableNames) > 0 {
			buf.WriteLeadingString("UPDATE ")
			buf.WriteStrings(tableNames, ", ")
		}

	default:
		if len(ub.tables) > 0 {
			buf.WriteLeadingString("UPDATE ")
			buf.WriteStrings(ub.tables, ", ")
		}
	}

	ub.injection.WriteTo(buf, updateMarkerAfterUpdate)

	if assignments := filterEmptyStrings(ub.assignments); len(assignments) > 0 {
		buf.WriteLeadingString("SET ")
		buf.WriteStrings(assignments, ", ")
	}

	ub.injection.WriteTo(buf, updateMarkerAfterSet)

	if flavor == SQLServer {
		if len(ub.returning) > 0 {
			buf.WriteLeadingString("OUTPUT ")
			buf.WriteStringsPrefixed("INSERTED.", ub.returning, ", ")
		}

		ub.injection.WriteTo(buf, updateMarkerAfterReturning)
	}

	if flavor != MySQL {
		// For ISO SQL, CTE table names should be written after FROM keyword.
		if ub.cteBuilder != nil {
			cteTableNames := ub.cteBuilder.tableNamesForFrom()

			if len(cteTableNames) > 0 {
				buf.WriteLeadingString("FROM ")
				buf.WriteStrings(cteTableNames, ", ")
			}
		}
	}

	if flavor == PostgreSQL || flavor == SQLite || flavor == SQLServer {
		if len(ub.fromTables) > 0 {

			if ub.cteBuilder == nil || len(ub.cteBuilder.tableNamesForFrom()) == 0 {
				buf.WriteLeadingString("FROM ")
			} else {
				buf.WriteString(", ")
			}

			buf.WriteStrings(ub.fromTables, ", ")
			ub.injection.WriteTo(buf, updateMarkerAfterFrom)
		}
	}

	if ub.WhereClause != nil {
		ub.whereClauseProxy.WhereClause = ub.WhereClause
		defer func() {
			ub.whereClauseProxy.WhereClause = nil
		}()

		buf.WriteLeadingString(ub.whereClauseExpr)
		ub.injection.WriteTo(buf, updateMarkerAfterWhere)
	}

	if len(ub.orderByCols) > 0 {
		buf.WriteLeadingString("ORDER BY ")
		buf.WriteStrings(ub.orderByCols, ", ")

		if ub.order != "" {
			buf.WriteLeadingString(ub.order)
		}

		ub.injection.WriteTo(buf, updateMarkerAfterOrderBy)
	}

	if len(ub.limitVar) > 0 {
		buf.WriteLeadingString("LIMIT ")
		buf.WriteString(ub.limitVar)

		ub.injection.WriteTo(buf, updateMarkerAfterLimit)
	}

	if flavor == PostgreSQL || flavor == SQLite {
		if len(ub.returning) > 0 {
			buf.WriteLeadingString("RETURNING ")
			buf.WriteStrings(ub.returning, ", ")
		}

		ub.injection.WriteTo(buf, updateMarkerAfterReturning)
	}

	return ub.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
}

// SetFlavor sets the flavor of compiled sql.
func (ub *UpdateBuilder) SetFlavor(flavor Flavor) (old Flavor) {
	old = ub.args.Flavor
	ub.args.Flavor = flavor
	return
}

// Flavor returns flavor of builder
func (ub *UpdateBuilder) Flavor() Flavor {
	return ub.args.Flavor
}

// SQL adds an arbitrary sql to current position.
func (ub *UpdateBuilder) SQL(sql string) *UpdateBuilder {
	ub.injection.SQL(ub.marker, sql)
	return ub
}
