package sqlbuilder

import (
	"context"
	"database/sql"

	"github.com/upper/db/v4"
	"github.com/upper/db/v4/internal/immutable"
	"github.com/upper/db/v4/internal/sqladapter/exql"
)

type deleterQuery struct {
	table string
	limit int

	where     *exql.Where
	whereArgs []interface{}

	amendFn func(string) string
}

func (dq *deleterQuery) and(b *sqlBuilder, terms ...interface{}) error {
	where, whereArgs := b.t.toWhereWithArguments(terms)

	if dq.where == nil {
		dq.where, dq.whereArgs = &exql.Where{}, []interface{}{}
	}
	dq.where.Append(&where)
	dq.whereArgs = append(dq.whereArgs, whereArgs...)

	return nil
}

func (dq *deleterQuery) statement() *exql.Statement {
	stmt := &exql.Statement{
		Type:  exql.Delete,
		Table: exql.TableWithName(dq.table),
	}

	if dq.where != nil {
		stmt.Where = dq.where
	}

	if dq.limit != 0 {
		stmt.Limit = exql.Limit(dq.limit)
	}

	stmt.SetAmendment(dq.amendFn)

	return stmt
}

type deleter struct {
	builder *sqlBuilder

	fn   func(*deleterQuery) error
	prev *deleter
}

var _ = immutable.Immutable(&deleter{})

func (del *deleter) SQL() *sqlBuilder {
	if del.prev == nil {
		return del.builder
	}
	return del.prev.SQL()
}

func (del *deleter) template() *exql.Template {
	return del.SQL().t.Template
}

func (del *deleter) String() string {
	s, err := del.Compile()
	if err != nil {
		panic(err.Error())
	}
	return prepareQueryForDisplay(s)
}

func (del *deleter) setTable(table string) *deleter {
	return del.frame(func(uq *deleterQuery) error {
		uq.table = table
		return nil
	})
}

func (del *deleter) frame(fn func(*deleterQuery) error) *deleter {
	return &deleter{prev: del, fn: fn}
}

func (del *deleter) Where(terms ...interface{}) db.Deleter {
	return del.frame(func(dq *deleterQuery) error {
		dq.where, dq.whereArgs = &exql.Where{}, []interface{}{}
		return dq.and(del.SQL(), terms...)
	})
}

func (del *deleter) And(terms ...interface{}) db.Deleter {
	return del.frame(func(dq *deleterQuery) error {
		return dq.and(del.SQL(), terms...)
	})
}

func (del *deleter) Limit(limit int) db.Deleter {
	return del.frame(func(dq *deleterQuery) error {
		dq.limit = limit
		return nil
	})
}

func (del *deleter) Amend(fn func(string) string) db.Deleter {
	return del.frame(func(dq *deleterQuery) error {
		dq.amendFn = fn
		return nil
	})
}

func (dq *deleterQuery) arguments() []interface{} {
	return joinArguments(dq.whereArgs)
}

func (del *deleter) Arguments() []interface{} {
	dq, err := del.build()
	if err != nil {
		return nil
	}
	return dq.arguments()
}

func (del *deleter) Prepare() (*sql.Stmt, error) {
	return del.PrepareContext(del.SQL().sess.Context())
}

func (del *deleter) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
	dq, err := del.build()
	if err != nil {
		return nil, err
	}
	return del.SQL().sess.StatementPrepare(ctx, dq.statement())
}

func (del *deleter) Exec() (sql.Result, error) {
	return del.ExecContext(del.SQL().sess.Context())
}

func (del *deleter) ExecContext(ctx context.Context) (sql.Result, error) {
	dq, err := del.build()
	if err != nil {
		return nil, err
	}
	return del.SQL().sess.StatementExec(ctx, dq.statement(), dq.arguments()...)
}

func (del *deleter) statement() (*exql.Statement, error) {
	iq, err := del.build()
	if err != nil {
		return nil, err
	}
	return iq.statement(), nil
}

func (del *deleter) build() (*deleterQuery, error) {
	dq, err := immutable.FastForward(del)
	if err != nil {
		return nil, err
	}
	return dq.(*deleterQuery), nil
}

func (del *deleter) Compile() (string, error) {
	s, err := del.statement()
	if err != nil {
		return "", err
	}
	return s.Compile(del.template())
}

func (del *deleter) Prev() immutable.Immutable {
	if del == nil {
		return nil
	}
	return del.prev
}

func (del *deleter) Fn(in interface{}) error {
	if del.fn == nil {
		return nil
	}
	return del.fn(in.(*deleterQuery))
}

func (del *deleter) Base() interface{} {
	return &deleterQuery{}
}
