package qx

import (
	"database/sql"
	"fmt"

	"gordenko.dev/dima/fixme"
)

const (
	ForeignKeyInsertError = "-1"
	ForeignKeyRemoveError = "-2"
	UniqueError           = "-3"
	DuplicateError        = "-4"
)

var (
	errorMessages = map[fixme.Code]string{
		ForeignKeyInsertError: "Foreign Key Insert Error",
		ForeignKeyRemoveError: "Foreign Key Remove Error",
		UniqueError:           "Unique Error",
		DuplicateError:        "Duplicate Error",
	}

	fix = fixme.New(errorMessages)
)

type Selector interface {
	One(interface{}) (bool, error)
	OneBy(interface{}, string, ...interface{}) (bool, error)
	OneQuery(interface{}, string, ...interface{}) (bool, error)
	List(interface{}) error
	ListBy(interface{}, string, ...interface{}) error
	ListQuery(interface{}, string, ...interface{}) error
	Has(string, string, ...interface{}) (bool, error)
}

// DB

//ForeignKeyErrorCodeOnRemove int

type Db struct {
	db         *sql.DB
	driver     Driver
	core       *Core
	driverName string
}

func Open(driverName, DSN string) (_ *Db, err error) {
	db, err := sql.Open(driverName, DSN)
	if err != nil {
		return
	}
	s := new(Db)
	s.db = db
	s.driverName = driverName

	switch driverName {
	case "mysql":
		s.driver = &mysql{}
	case "oracle", "goracle":
		s.driver = &oracle{}
	default:
		err = fmt.Errorf("unknown driver name %q", driverName)
		return
	}
	s.core = newCore(s.driver)
	return s, nil
}

func (s *Db) Close() error {
	return s.db.Close()
}

func (s *Db) DriverName() string {
	return s.driverName
}

func (s *Db) Driver() *sql.DB {
	return s.db
}

func (s *Db) RegisterDMTypes(objects ...interface{}) error {
	return s.core.RegisterDMTypes(objects)
}

func (s *Db) Tx() (tx *Tx, err error) {
	rawTx, err := s.db.Begin()
	if err != nil {
		return
	}
	return &Tx{
		tx:     rawTx,
		driver: s.driver,
		core:   s.core,
	}, nil
}

func (s *Db) Exec(query string, args ...interface{}) (err error) {
	tx, err := s.Tx()
	if err != nil {
		return
	}
	defer tx.Close(&err)

	err = tx.Exec(query, args...)
	if err != nil {
		return
	}
	err = tx.Commit()
	return
}

func (s *Db) ExecInsert(seq string, query string, args ...interface{}) (id int64, err error) {
	tx, err := s.Tx()
	if err != nil {
		return
	}
	defer tx.Close(&err)

	id, err = tx.ExecInsert(seq, query, args...)
	if err != nil {
		return
	}
	err = tx.Commit()
	return
}

func (s *Db) Insert(obj interface{}) (id int64, err error) {
	tx, err := s.Tx()
	if err != nil {
		return
	}
	defer tx.Close(&err)

	id, err = tx.Insert(obj)
	if err != nil {
		return
	}
	err = tx.Commit()
	return
}

func (s *Db) Remove(obj interface{}) (err error) {
	tx, err := s.Tx()
	if err != nil {
		return
	}
	defer tx.Close(&err)

	err = tx.Remove(obj)
	if err != nil {
		return
	}
	return tx.Commit()
}

func (s *Db) Update(obj interface{}) (err error) {
	tx, err := s.Tx()
	if err != nil {
		return
	}
	defer tx.Close(&err)

	err = tx.Update(obj)
	if err != nil {
		return
	}
	return tx.Commit()
}

func (s *Db) List(list interface{}) (err error) {
	return s.core.List(s.db.Query, list)
}

func (s *Db) ListBy(list interface{}, querySuffix string, args ...interface{}) (err error) {
	return s.core.ListBy(s.db.Query, list, querySuffix, args)
}

func (s *Db) ListQuery(list interface{}, rawQuery string, args ...interface{}) (err error) {
	return s.core.ListQuery(s.db.Query, list, rawQuery, args)
}

func (s *Db) OneBy(one interface{}, querySuffix string, args ...interface{}) (found bool, err error) {
	return s.core.OneBy(s.db.QueryRow, one, querySuffix, args)
}

func (s *Db) One(one interface{}) (found bool, err error) {
	return s.core.One(s.db.QueryRow, one)
}

// OneQuery - SELECT должен возвращать одну колонку. Аргумент rawQuery - это
// полный SQL запрос.
func (s *Db) OneQuery(one interface{}, rawQuery string, args ...interface{}) (found bool, err error) {
	return s.core.OneQuery(s.db.QueryRow, one, rawQuery, args)
}

func (s *Db) Has(table string, where string, args ...interface{}) (has bool, err error) {
	return s.core.Has(hasIn{
		Table:        table,
		Where:        where,
		Args:         args,
		QueryRowFunc: s.db.QueryRow,
	})
}
