// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// Package db (or upper-db) provides a common interface to work with different
// data sources using adapters that wrap mature database drivers.
//
// The main purpose of upper-db is to abstract common database operations and
// encourage users perform advanced operations directly using the underlying
// driver. upper-db supports the MySQL, PostgreSQL, SQLite and QL databases and
// provides partial support (CRUD, no transactions) for MongoDB.
//
//  go get upper.io/db.v3
//
// Usage
//
//  package main
//
//  import (
//  	"log"
//
//  	"upper.io/db.v3/postgresql" // Imports the postgresql adapter.
//  )
//
//  var settings = postgresql.ConnectionURL{
//  	Database: `booktown`,
//  	Host:     `demo.upper.io`,
//  	User:     `demouser`,
//  	Password: `demop4ss`,
//  }
//
//  // Book represents a book.
//  type Book struct {
//  	ID        uint   `db:"id"`
//  	Title     string `db:"title"`
//  	AuthorID  uint   `db:"author_id"`
//  	SubjectID uint   `db:"subject_id"`
//  }
//
//  func main() {
//  	sess, err := postgresql.Open(settings)
//  	if err != nil {
//  		log.Fatal(err)
//  	}
//  	defer sess.Close()
//
//  	var books []Book
//  	if err := sess.Collection("books").Find().OrderBy("title").All(&books); err != nil {
//  		log.Fatal(err)
//  	}
//
//  	log.Println("Books:")
//  	for _, book := range books {
//  		log.Printf("%q (ID: %d)\n", book.Title, book.ID)
//  	}
//  }
//
// See more usage examples and documentation for users at
// https://upper.io/db.v3.
package db // import "upper.io/db.v3"

import (
	"fmt"
	"reflect"
	"time"

	"upper.io/db.v3/internal/immutable"
)

// Constraint interface represents a single condition, like "a = 1".  where `a`
// is the key and `1` is the value. This is an exported interface but it's
// rarely used directly, you may want to use the `db.Cond{}` map instead.
type Constraint interface {
	// Key is the leftmost part of the constraint and usually contains a column
	// name.
	Key() interface{}

	// Value if the rightmost part of the constraint and usually contains a
	// column value.
	Value() interface{}
}

// Constraints interface represents an array or constraints, like "a = 1, b =
// 2, c = 3".
type Constraints interface {
	// Constraints returns an array of constraints.
	Constraints() []Constraint
}

// Compound represents an statement that has one or many sentences joined by by
// an operator like "AND" or "OR". This is an exported interface but it's
// rarely used directly, you may want to use the `db.And()` or `db.Or()`
// functions instead.
type Compound interface {
	// Sentences returns child sentences.
	Sentences() []Compound

	// Operator returns the operator that joins the compound's child sentences.
	Operator() CompoundOperator

	// Empty returns true if the compound has zero children, false otherwise.
	Empty() bool
}

// CompoundOperator represents the operation on a compound statement.
type CompoundOperator uint

// Compound operators.
const (
	OperatorNone CompoundOperator = iota
	OperatorAnd
	OperatorOr
)

// RawValue interface represents values that can bypass SQL filters. This is an
// exported interface but it's rarely used directly, you may want to use
// the `db.Raw()` function instead.
type RawValue interface {
	fmt.Stringer
	Compound

	// Raw returns the string representation of the value that the user wants to
	// pass without any escaping.
	Raw() string

	// Arguments returns the arguments to be replaced on the query.
	Arguments() []interface{}
}

// Function interface defines methods for representing database functions.
// This is an exported interface but it's rarely used directly, you may want to
// use the `db.Func()` function instead.
type Function interface {
	// Name returns the function name.
	Name() string

	// Argument returns the function arguments.
	Arguments() []interface{}
}

// Marshaler is the interface implemented by struct fields that can transform
// themselves into values that can be stored on a database.
type Marshaler interface {
	// MarshalDB returns the internal database representation of the Go value.
	MarshalDB() (interface{}, error)
}

// Unmarshaler is the interface implemented by struct fields that can transform
// themselves from stored database values into Go values.
type Unmarshaler interface {
	// UnmarshalDB receives an internal database representation of a value and
	// must transform that into a Go value.
	UnmarshalDB(interface{}) error
}

// Cond is a map that defines conditions for a query and satisfies the
// Constraints and Compound interfaces.
//
// Each entry of the map represents a condition (a column-value relation bound
// by a comparison operator). The comparison operator is optional and can be
// specified after the column name, if no comparison operator is provided the
// equality is used.
//
// Examples:
//
//  // Where age equals 18.
//  db.Cond{"age": 18}
//  //	// Where age is greater than or equal to 18.
//  db.Cond{"age >=": 18}
//
//  // Where id is in a list of ids.
//  db.Cond{"id IN": []{1, 2, 3}}
//
//  // Where age is lower than 18 (you could use this syntax when using
//  // mongodb).
//  db.Cond{"age $lt": 18}
//
//  // Where age > 32 and age < 35
//  db.Cond{"age >": 32, "age <": 35}
type Cond map[interface{}]interface{}

// Constraints returns each one of the Cond map records as a constraint.
func (c Cond) Constraints() []Constraint {
	z := make([]Constraint, 0, len(c))
	for k, v := range c {
		z = append(z, NewConstraint(k, v))
	}
	return z
}

// Sentences return each one of the map records as a compound.
func (c Cond) Sentences() []Compound {
	z := make([]Compound, 0, len(c))
	for k, v := range c {
		z = append(z, Cond{k: v})
	}
	return z
}

// Operator returns the default compound operator.
func (c Cond) Operator() CompoundOperator {
	return OperatorNone
}

// Empty returns false if there are no conditions.
func (c Cond) Empty() bool {
	for range c {
		return false
	}
	return true
}

type rawValue struct {
	v string
	a *[]interface{} // This may look ugly but allows us to use db.Raw() as keys for db.Cond{}.
}

func (r rawValue) Arguments() []interface{} {
	if r.a != nil {
		return *r.a
	}
	return nil
}

func (r rawValue) Raw() string {
	return r.v
}

func (r rawValue) String() string {
	return r.Raw()
}

// Sentences return each one of the map records as a compound.
func (r rawValue) Sentences() []Compound {
	return []Compound{r}
}

// Operator returns the default compound operator.
func (r rawValue) Operator() CompoundOperator {
	return OperatorNone
}

// Empty return false if this struct holds no value.
func (r rawValue) Empty() bool {
	return r.v == ""
}

type compound struct {
	prev *compound
	fn   func(*[]Compound) error
}

func newCompound(conds ...Compound) *compound {
	c := &compound{}
	if len(conds) == 0 {
		return c
	}
	return c.frame(func(in *[]Compound) error {
		*in = append(*in, conds...)
		return nil
	})
}

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

func (c *compound) Sentences() []Compound {
	conds, err := immutable.FastForward(c)
	if err == nil {
		return *(conds.(*[]Compound))
	}
	return nil
}

func (c *compound) Operator() CompoundOperator {
	return OperatorNone
}

func (c *compound) Empty() bool {
	if c.fn != nil {
		return false
	}
	if c.prev != nil {
		return c.prev.Empty()
	}
	return true
}

func (c *compound) frame(fn func(*[]Compound) error) *compound {
	return &compound{prev: c, fn: fn}
}

func (c *compound) Prev() immutable.Immutable {
	if c == nil {
		return nil
	}
	return c.prev
}

func (c *compound) Fn(in interface{}) error {
	if c.fn == nil {
		return nil
	}
	return c.fn(in.(*[]Compound))
}

func (c *compound) Base() interface{} {
	return &[]Compound{}
}

func defaultJoin(in ...Compound) []Compound {
	for i := range in {
		if cond, ok := in[i].(Cond); ok && len(cond) > 1 {
			in[i] = And(cond)
		}
	}
	return in
}

// Union represents a compound joined by OR.
type Union struct {
	*compound
}

// Or adds more terms to the compound.
func (o *Union) Or(orConds ...Compound) *Union {
	var fn func(*[]Compound) error
	if len(orConds) > 0 {
		fn = func(in *[]Compound) error {
			*in = append(*in, orConds...)
			return nil
		}
	}
	return &Union{o.compound.frame(fn)}
}

// Operator returns the OR operator.
func (o *Union) Operator() CompoundOperator {
	return OperatorOr
}

// Empty returns false if this struct holds no conditions.
func (o *Union) Empty() bool {
	return o.compound.Empty()
}

// And adds more terms to the compound.
func (a *Intersection) And(andConds ...Compound) *Intersection {
	var fn func(*[]Compound) error
	if len(andConds) > 0 {
		fn = func(in *[]Compound) error {
			*in = append(*in, andConds...)
			return nil
		}
	}
	return &Intersection{a.compound.frame(fn)}
}

// Empty returns false if this struct holds no conditions.
func (a *Intersection) Empty() bool {
	return a.compound.Empty()
}

// Intersection represents a compound joined by AND.
type Intersection struct {
	*compound
}

// Operator returns the AND operator.
func (a *Intersection) Operator() CompoundOperator {
	return OperatorAnd
}

type constraint struct {
	k interface{}
	v interface{}
}

func (c constraint) Key() interface{} {
	return c.k
}

func (c constraint) Value() interface{} {
	return c.v
}

// NewConstraint creates a constraint.
func NewConstraint(key interface{}, value interface{}) Constraint {
	return constraint{k: key, v: value}
}

// Func represents a database function and satisfies the db.Function interface.
//
// Examples:
//
//	// MOD(29, 9)
//	db.Func("MOD", 29, 9)
//
//	// CONCAT("foo", "bar")
//	db.Func("CONCAT", "foo", "bar")
//
//	// NOW()
//	db.Func("NOW")
//
//	// RTRIM("Hello  ")
//	db.Func("RTRIM", "Hello  ")
func Func(name string, args ...interface{}) Function {
	if len(args) == 1 {
		if reflect.TypeOf(args[0]).Kind() == reflect.Slice {
			iargs := make([]interface{}, len(args))
			for i := range args {
				iargs[i] = args[i]
			}
			args = iargs
		}
	}
	return &dbFunc{name: name, args: args}
}

type dbFunc struct {
	name string
	args []interface{}
}

func (f *dbFunc) Arguments() []interface{} {
	return f.args
}

func (f *dbFunc) Name() string {
	return f.name
}

// And joins conditions under logical conjunction. Conditions can be
// represented by db.Cond{}, db.Or() or db.And().
//
// Examples:
//
//	// name = "Peter" AND last_name = "Parker"
//	db.And(
//		db.Cond{"name": "Peter"},
//		db.Cond{"last_name": "Parker "},
//	)
//
//	// (name = "Peter" OR name = "Mickey") AND last_name = "Mouse"
//	db.And(
//		db.Or(
//			db.Cond{"name": "Peter"},
//			db.Cond{"name": "Mickey"},
//		),
//		db.Cond{"last_name": "Mouse"},
//	)
func And(conds ...Compound) *Intersection {
	return &Intersection{newCompound(conds...)}
}

// Or joins conditions under logical disjunction. Conditions can be represented
// by db.Cond{}, db.Or() or db.And().
//
// Example:
//
//	// year = 2012 OR year = 1987
//	db.Or(
//		db.Cond{"year": 2012},
//		db.Cond{"year": 1987},
//	)
func Or(conds ...Compound) *Union {
	return &Union{newCompound(defaultJoin(conds...)...)}
}

// Raw marks chunks of data as protected, so they pass directly to the query
// without any filtering. Use with care.
//
// Example:
//
//	// SOUNDEX('Hello')
//	Raw("SOUNDEX('Hello')")
//
// Raw returns a value that satifies the db.RawValue interface.
func Raw(value string, args ...interface{}) RawValue {
	r := rawValue{v: value, a: nil}
	if len(args) > 0 {
		r.a = &args
	}
	return r
}

// Database is an interface that defines methods that must be satisfied by
// all database adapters.
type Database interface {
	// Driver returns the underlying driver the wrapper uses.
	//
	// In order to actually use the driver, the `interface{}` value needs to be
	// casted into the appropriate type.
	//
	// Example:
	//  internalSQLDriver := sess.Driver().(*sql.DB)
	Driver() interface{}

	// Open attempts to establish a connection with a DBMS.
	Open(ConnectionURL) error

	// Clone duplicates the current database session. Returns an error if the
	// clone did not succeed.
	// Clone() (Database, error)

	// Ping returns an error if the database manager could be reached.
	Ping() error

	// Close closes the currently active connection to the database and clears
	// caches.
	Close() error

	// Collection returns a collection reference given a table name.
	Collection(string) Collection

	// Collections returns the names of all non-system tables on the database.
	Collections() ([]string, error)

	// Name returns the name of the active database.
	Name() string

	// ConnectionURL returns the data used to set up the adapter.
	ConnectionURL() ConnectionURL

	// ClearCache clears all the cache mechanisms the adapter is using.
	ClearCache()

	// SetConnMaxLifetime sets the maximum amount of time a connection may be
	// reused.
	SetConnMaxLifetime(time.Duration)

	// SetMaxIdleConns sets the maximum number of connections in the idle
	// connection pool.
	SetMaxIdleConns(int)

	// SetMaxOpenConns sets the maximum number of open connections to the
	// database.
	SetMaxOpenConns(int)
}

// Tx has methods for transactions that can be either committed or rolled back.
type Tx interface {
	// Rollback discards all the instructions on the current transaction.
	Rollback() error

	// Commit commits the current transaction.
	Commit() error
}

// Collection is an interface that defines methods useful for handling tables.
type Collection interface {
	// Insert inserts a new item into the collection, it accepts one argument
	// that can be either a map or a struct. If the call suceeds, it returns the
	// ID of the newly added element as an `interface{}` (the underlying type of
	// this ID is unknown and depends on the database adapter). The ID returned
	// by Insert() could be passed directly to Find() to retrieve the newly added
	// element.
	Insert(interface{}) (interface{}, error)

	// InsertReturning is like Insert() but it updates the passed pointer to map
	// or struct with the newly inserted element (and with automatic fields, like
	// IDs, timestamps, etc). This is all done atomically within a transaction.
	// If the database does not support transactions this method returns
	// db.ErrUnsupported.
	InsertReturning(interface{}) error

	// Exists returns true if the collection exists, false otherwise.
	Exists() bool

	// Find defines a new result set with elements from the collection.
	Find(...interface{}) Result

	// Truncate removes all elements on the collection and resets the
	// collection's IDs.
	Truncate() error

	// Name returns the name of the collection.
	Name() string
}

// Result is an interface that defines methods useful for working with result
// sets.
type Result interface {
	// String satisfies fmt.Stringer and returns a SELECT statement for
	// the result.
	String() string

	// Limit defines the maximum number of results in this set. It only has
	// effect on `One()`, `All()` and `Next()`.
	Limit(int) Result

	// Offset ignores the first *n* results. It only has effect on `One()`, `All()`
	// and `Next()`.
	Offset(int) Result

	// OrderBy receives field names that define the order in which elements will be
	// returned in a query, field names may be prefixed with a minus sign (-)
	// indicating descending order, ascending order will be used otherwise.
	OrderBy(...interface{}) Result

	// Select defines specific columns to be returned from the elements of the
	// set.
	Select(...interface{}) Result

	// Where discards all the previously set filtering constraints (if any) and
	// sets new ones. Commonly used when the conditions of the result depend on
	// external parameters that are yet to be evaluated:
	//
	//   res := col.Find()
	//
	//   if ... {
	//     res.Where(...)
	//   } else {
	//     res.Where(...)
	//   }
	Where(...interface{}) Result

	// And adds more filtering conditions on top of the existing constraints.
	//
	//   res := col.Find(...).And(...)
	And(...interface{}) Result

	// Group is used to group results that have the same value in the same column
	// or columns.
	Group(...interface{}) Result

	// Delete deletes all items within the result set. `Offset()` and `Limit()` are
	// not honoured by `Delete()`.
	Delete() error

	// Update modifies all items within the result set. `Offset()` and `Limit()`
	// are not honoured by `Update()`.
	Update(interface{}) error

	// Count returns the number of items that match the set conditions. `Offset()`
	// and `Limit()` are not honoured by `Count()`
	Count() (uint64, error)

	// Next fetches the next result within the result set and dumps it into the
	// given pointer to struct or pointer to map. You must call
	// `Close()` after finishing using `Next()`.
	Next(ptrToStruct interface{}) bool

	// Err returns the last error that has happened with the result set, nil
	// otherwise.
	Err() error

	// One fetches the first result within the result set and dumps it into the
	// given pointer to struct or pointer to map. The result set is automatically
	// closed after picking the element, so there is no need to call Close()
	// after using One().
	One(ptrToStruct interface{}) error

	// All fetches all results within the result set and dumps them into the
	// given pointer to slice of maps or structs.  The result set is
	// automatically closed, so there is no need to call Close() after
	// using All().
	All(sliceOfStructs interface{}) error

	// Close closes the result set and frees all locked resources.
	Close() error
}

// ConnectionURL represents a connection string.
type ConnectionURL interface {
	// String returns the connection string that is going to be passed to the
	// adapter.
	String() string
}

var (
	_ Function    = &dbFunc{}
	_ Constraints = Cond{}
	_ Compound    = Cond{}
	_ Constraint  = &constraint{}
	_ RawValue    = &rawValue{}
)