package qx

import (
	"database/sql"
	"fmt"
	"log"
	"reflect"
	"testing"
	"web/api"

	"github.com/kr/pretty"

	_ "github.com/go-sql-driver/mysql"
)

// Для тестов, создаем в init функции
var (
	db    *Db
	rawdb *sql.DB
)

func init() {
	var err error
	db, err = Open("mysql", "root:zkx75fcy@/qx")
	if err != nil {
		log.Fatal(err)
	}

	rawdb, err = sql.Open("mysql", "root:zkx75fcy@/qx")
	if err != nil {
		log.Fatal(err)
	}
}

var (
	connectorID    = 1
	firstDeviceID  = 11
	secondDeviceID = 22

	firstUsername  = "inagro"
	secondUsername = "vodokanal"

	testIMEI = "12345"

	testUser = User{
		Username: firstUsername,
		Name:     "Inagro ltd.",
		Status:   7,
		Email:    "a@b.com",
	}

	testUser2 = User{
		Username: secondUsername,
		Name:     "Vodokanal ltd.",
		Status:   3,
		Email:    "xx@zz.com",
	}

	testConnector = Connector{
		ConnectorID: connectorID,
		Name:        "ain",
	}

	testFirstDevice = Device{
		DeviceID:    11,
		Name:        "Sharky",
		ConnectorID: connectorID,
	}

	testSecondDevice = Device{
		DeviceID:    secondDeviceID,
		Name:        "Sharky v2",
		ConnectorID: connectorID,
	}

	testDevice = testFirstDevice // alias

	testModem = Modem{
		IMEI:     testIMEI,
		Username: firstUsername,
		Devices: []ModemDevice{
			ModemDevice{
				Addr:     1,
				DeviceID: firstDeviceID,
				Params: []ModemDeviceParam{
					ModemDeviceParam{
						Name:  "login",
						Value: "inagro",
					},
					ModemDeviceParam{
						Name:  "password",
						Value: "pwd",
					},
				},
			},
			ModemDevice{
				Addr:     2,
				DeviceID: secondDeviceID,
				Params: []ModemDeviceParam{
					ModemDeviceParam{
						Name:  "serialNo",
						Value: "X365BT",
					},
				},
			},
		},
	}
	// аргумент для IN запросов
	connectorIDs = []int{
		1000,
		testConnector.ConnectorID,
		2000,
	}

	testDeviceBrief = DeviceBrief{
		DeviceID:  testSecondDevice.DeviceID,
		Name:      testSecondDevice.Name,
		Connector: testConnector,
	}
)

func clear() (err error) {
	var tables = []string{"modems", "devices", "connectors", "users"}
	tx, err := rawdb.Begin()
	if err != nil {
		return
	}
	for _, table := range tables {
		_, err = tx.Exec("DELETE FROM " + table)
		if err != nil {
			return
		}
	}
	err = tx.Commit()
	if err != nil {
		tx.Rollback()
	}
	return
}

func assertApiError(estimateErr api.Err, returnedErr error) (err error) {
	if returnedErr == nil {
		return fmt.Errorf("Expected api.Err, but call was successful")
	}
	apierr, ok := returnedErr.(api.Err)
	if !ok {
		return fmt.Errorf("Expected api.Err, got %s", returnedErr)
	}

	if !reflect.DeepEqual(apierr, estimateErr) {
		return fmt.Errorf("\nEstimate: %# v\nReturned: %# v",
			pretty.Formatter(estimateErr), pretty.Formatter(apierr))
	}
	return
}

type User struct {
	Username string `json:"username" qx:"table=users;pk"`
	Name     string `json:"name" qx:"unique"`
	Status   int    `json:"status"`
	Email    string `json:"email" qx:"unique"`
}

func getUser(username string) (r User, err error) {
	err = rawdb.QueryRow(`SELECT username, name, status, email FROM users WHERE username=?`,
		username).Scan(&r.Username, &r.Name, &r.Status, &r.Email)
	return
}

// Тестируем успешный Insert с проверкой Unique.
// PrimaryKey - 1 поле, строка.
func TestInsertWithUnique(t *testing.T) {
	var (
		err    error
		origin = testUser
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(origin)
	if err != nil {
		t.Fatal(err)
	}

	r, err := getUser(origin.Username)
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(origin, r) {
		t.Fatalf("\nOrigin: %# v\nResult: %# v\n",
			pretty.Formatter(origin), pretty.Formatter(r))
	}
}

// Тестируем ошибку Unique. Проверяем что возвращается api.Err с правильным
// кодом и именем поля.
func TestInsertUniqueViolation(t *testing.T) {
	var (
		err         error
		origin      = testUser
		estimateErr = api.Err{
			Field: "name",
			Code:  UniqueError,
		}
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(origin)
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(origin)

	err = assertApiError(estimateErr, err)
	if err != nil {
		t.Fatal(err)
	}
}

type Connector struct {
	ConnectorID int    `json:"connectorID" qx:"table=connectors; pk; seq=conn_id"`
	Name        string `json:"name" qx:"unique"`
}

type Device struct {
	DeviceID    int    `json:"deviceID" qx:"table=devices; pk; seq=dev_id"`
	Name        string `json:"name" qx:"unique=name,connectorID"`
	ConnectorID int    `json:"connectorID" qx:"fk=connectors"`
}

// Тестируем ошибку ForeignKey при Insert. Проверяем что возвращается api.Err
// с правильным кодом и именем поля.
func TestInsertForeignKeyViolation(t *testing.T) {
	var (
		err    error
		origin = testFirstDevice
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	estimateErr := api.Err{
		Field: "connectorID",
		Code:  ForeignKeyInsertError,
	}

	_, err = db.Insert(origin)
	err = assertApiError(estimateErr, err)
	if err != nil {
		t.Fatal(err)
	}
}

// Тестируем успешный Insert с проверкой CompositeUnique и ForeignKey.
func TestInsertWithForeignKeyAndCompositeUnique(t *testing.T) {
	var (
		err          error
		lastInsertID int64
		origin       = testFirstDevice
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(testConnector)
	if err != nil {
		t.Fatal(err)
	}

	lastInsertID, err = db.Insert(origin)
	if err != nil {
		t.Fatal(err)
	}

	origin.DeviceID = int(lastInsertID)

	var r Device
	err = rawdb.QueryRow(`SELECT deviceID, name, connectorID FROM devices`).Scan(
		&r.DeviceID, &r.Name, &r.ConnectorID)
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(origin, r) {
		t.Fatalf("\nOrigin: %# v\nResult: %# v\n",
			pretty.Formatter(origin), pretty.Formatter(r))
	}
}

// Тестируем ошибку CompositeUnique. Проверяем что возвращается api.Err с правильным
// кодом и именем поля.
func TestInsertCompositeUniqueViolation(t *testing.T) {
	var (
		err         error
		origin      = testFirstDevice
		estimateErr = api.Err{
			Field: "name",
			Code:  UniqueError,
		}
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(testConnector)
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(origin)
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(origin)

	err = assertApiError(estimateErr, err)
	if err != nil {
		t.Fatal(err)
	}
}

type Modem struct {
	IMEI     string        `json:"imei" qx:"table=modems; pk"`
	Username string        `json:"username" qx:"fk=users"`
	Devices  []ModemDevice `json:"devices"`
}

//IMEI     string `json:"imei" qx:"unique"`
type ModemDevice struct {
	DeviceNo int                `json:"deviceNo" qx:"table=modemDevices; pk; seq=gen_deviceno_id"`
	Addr     int                `json:"addr"`
	DeviceID int                `json:"deviceID" qx:"fk=devices"`
	Params   []ModemDeviceParam `json:"params"`
}

//DeviceNo int    `json:"deviceNo" qx:"table=modemDeviceParams; fk=modemDevices; pk=deviceNo,name"`
type ModemDeviceParam struct {
	Name  string `json:"name" qx:"table=modemDeviceParams"`
	Value string `json:"value"`
}

func insertModem(origin Modem) (err error) {
	err = clear()
	if err != nil {
		return
	}

	// Добавляем юзера
	_, err = db.Insert(testUser)
	if err != nil {
		return
	}

	// Добавляем коннектор
	_, err = db.Insert(testConnector)
	if err != nil {
		return
	}
	// Добавляем 2 прибора
	_, err = db.Insert(testFirstDevice)
	if err != nil {
		return
	}

	_, err = db.Insert(testSecondDevice)
	if err != nil {
		return
	}
	// Тестовый insert
	_, err = db.Insert(origin)
	return
}

func getModem() (r Modem, err error) {
	err = rawdb.QueryRow(`SELECT imei, username FROM modems`).Scan(&r.IMEI, &r.Username)
	if err != nil {
		return
	}

	// Читаем список приборов
	rows, err := rawdb.Query(`
		SELECT deviceNo, addr, deviceID
		FROM modemDevices
		WHERE imei=?
		ORDER BY deviceNo ASC`, r.IMEI)
	if err != nil {
		return
	}
	defer rows.Close()

	for rows.Next() {
		var d ModemDevice
		err = rows.Scan(&d.DeviceNo, &d.Addr, &d.DeviceID)
		if err != nil {
			return
		}
		r.Devices = append(r.Devices, d)
	}
	if err = rows.Err(); err != nil {
		return
	}

	// Для каждого прибора читаем список параметров
	for idx, d := range r.Devices {
		// Сортируем name ASC, чтобы порядок параметров совпал с origin объектом
		rows, err = rawdb.Query(`
		SELECT name, value
		FROM modemDeviceParams
		WHERE deviceNo=?
		ORDER BY name ASC`, d.DeviceNo)
		if err != nil {
			return
		}
		defer rows.Close()

		for rows.Next() {
			var p ModemDeviceParam
			err = rows.Scan(&p.Name, &p.Value)
			if err != nil {
				return
			}
			d.Params = append(d.Params, p)
		}
		if err = rows.Err(); err != nil {
			return
		}

		d.DeviceNo = 0
		r.Devices[idx] = d
	}
	return
}

func TestInsert3LevelObject(t *testing.T) {
	var (
		err    error
		origin = testModem
	)

	err = insertModem(origin)
	if err != nil {
		t.Fatal(err)
	}

	// Читаем данные из БД
	r, err := getModem()
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(origin, r) {
		t.Fatalf("\nOrigin: %# v\nResult: %# v\n",
			pretty.Formatter(origin), pretty.Formatter(r))
	}
}

// Тестируем DuplicateError при Insert с AutoIncrement первичным ключем.
func TestInsertDuplicateErrorWithAutoIncrementPrimaryKey(t *testing.T) {
	var (
		err   error
		first = Connector{
			ConnectorID: 1,
			Name:        "First",
		}
		second = Connector{
			ConnectorID: 1,
			Name:        "Second",
		}
		estimateError = api.Err{
			Code: DuplicateError,
		}
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(first)
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(second)
	assertApiError(estimateError, err)
}

func TestInsertDuplicateError(t *testing.T) {
	var (
		err           error
		first         = testUser
		estimateError = api.Err{
			Code: DuplicateError,
		}
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(first)
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(first)
	assertApiError(estimateError, err)
}

// REMOVE
func TestRemoveByCustomPrimaryKey(t *testing.T) {
	var (
		err    error
		origin = testUser
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(origin)
	if err != nil {
		t.Fatal(err)
	}

	err = db.Remove(User{
		Username: origin.Username,
	})
	if err != nil {
		t.Fatal(err)
	}

	var expectZero int
	err = rawdb.QueryRow(`SELECT 1 FROM users WHERE username=?`,
		origin.Username).Scan(&expectZero)
	if err != nil {
		if err != sql.ErrNoRows {
			t.Fatal(err)
		}
		err = nil
	}

	if expectZero != 0 {
		t.Fatal("Object not removed\n")
	}
}

func TestRemoveByAutoIncrementPrimaryKey(t *testing.T) {
	var (
		err          error
		lastInsertID int64
		origin       = testConnector
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	lastInsertID, err = db.Insert(origin)
	if err != nil {
		t.Fatal(err)
	}

	err = db.Remove(Connector{
		ConnectorID: int(lastInsertID),
	})
	if err != nil {
		t.Fatal(err)
	}

	var expectZero int
	err = rawdb.QueryRow(`SELECT 1 FROM connectors WHERE connectorID=?`,
		lastInsertID).Scan(&expectZero)
	if err != nil {
		if err != sql.ErrNoRows {
			t.Fatal(err)
		}
		err = nil
	}

	if expectZero != 0 {
		t.Fatal("Object not removed\n")
	}
}

// UPDATE

func TestUpdateByAutoIncrementPrimaryKey(t *testing.T) {
	var (
		err          error
		lastInsertID int64
		origin       = testConnector
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	lastInsertID, err = db.Insert(origin)
	if err != nil {
		t.Fatal(err)
	}

	upd := Connector{
		ConnectorID: int(lastInsertID),
		Name:        "updated",
	}

	err = db.Update(upd)
	if err != nil {
		t.Fatal(err)
	}

	var r Connector
	err = rawdb.QueryRow(`SELECT connectorID, name FROM connectors WHERE connectorID=?`,
		lastInsertID).Scan(&r.ConnectorID, &r.Name)
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(upd, r) {
		t.Fatalf("\nOrigin: %# v\nResult: %# v\n",
			pretty.Formatter(upd), pretty.Formatter(r))
	}
}

func TestUpdateByCustomPrimaryKey(t *testing.T) {
	var (
		err error
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	_, err = db.Insert(testUser)
	if err != nil {
		t.Fatal(err)
	}

	upd := User{
		Username: "inagro",
		Name:     "Dima ltd.",
		Status:   3,
		Email:    "xx@zz.com",
	}

	err = db.Update(upd)
	if err != nil {
		t.Fatal(err)
	}

	r, err := getUser(testUser.Username)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(upd, r) {
		t.Fatalf("\nOrigin: %# v\nResult: %# v\n",
			pretty.Formatter(upd), pretty.Formatter(r))
	}
}

func TestUpdate3LevelObject(t *testing.T) {
	var (
		err error
	)
	// Вставляем модем
	err = insertModem(testModem)
	if err != nil {
		t.Fatal(err)
	}
	// Вставляем второго юзера, ибо будем обновлять ссылку на юзера
	_, err = db.Insert(testUser2)
	if err != nil {
		t.Fatal(err)
	}

	upd := Modem{
		IMEI:     testIMEI,
		Username: secondUsername,
		Devices: []ModemDevice{
			ModemDevice{
				Addr:     2,
				DeviceID: secondDeviceID,
				Params: []ModemDeviceParam{
					ModemDeviceParam{
						Name:  "init",
						Value: "2.5",
					},
					ModemDeviceParam{
						Name:  "serialNo",
						Value: "Z65FC",
					},
				},
			},
			ModemDevice{
				Addr:     3,
				DeviceID: firstDeviceID,
				Params: []ModemDeviceParam{
					ModemDeviceParam{
						Name:  "tariff",
						Value: "10",
					},
				},
			},
		},
	}

	err = db.Update(upd)
	if err != nil {
		t.Fatal(err)
	}

	r, err := getModem()
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(upd, r) {
		t.Fatalf("\nOrigin: %# v\nResult: %# v\n",
			pretty.Formatter(upd), pretty.Formatter(r))
	}
}

func TestUpdateForeignKeyViolation(t *testing.T) {
	var (
		err error
		upd = Modem{
			IMEI:     testIMEI,
			Username: "UnknownUsername",
		}
		estimateErr = api.Err{
			Field: "username",
			Code:  ForeignKeyInsertError,
		}
	)
	// Вставляем модем
	err = insertModem(testModem)
	if err != nil {
		t.Fatal(err)
	}

	err = db.Update(upd)

	err = assertApiError(estimateErr, err)
	if err != nil {
		t.Fatal(err)
	}
}

// Помимо проверки ForeignKey ошибки во вложенном объекте, проверяет правильность
// api.Err, включая Path (поле, индекс)
func TestUpdateNestedForeignKeyViolation(t *testing.T) {
	var (
		err error
		upd = Modem{
			IMEI:     testIMEI,
			Username: firstUsername,
			Devices: []ModemDevice{
				ModemDevice{
					Addr:     1,
					DeviceID: 11,
				},
				ModemDevice{
					Addr:     2,
					DeviceID: 1000, // unknown deviceID
				},
			},
		}
		estimateErr = api.Err{
			Field: "deviceID",
			Code:  ForeignKeyInsertError,
			Path: []api.Leg{
				api.Leg{
					Field: "devices",
					Idx:   1,
				},
			},
		}
	)
	// Вставляем модем
	err = insertModem(testModem)
	if err != nil {
		t.Fatal(err)
	}

	err = db.Update(upd)

	err = assertApiError(estimateErr, err)
	if err != nil {
		t.Fatal(err)
	}
}

// SELECT

func rawInsert(funcs ...func(*sql.Tx) error) (err error) {
	tx, err := rawdb.Begin()
	if err != nil {
		return
	}
	for _, f := range funcs {
		err = f(tx)
		if err != nil {
			return
		}
	}
	err = tx.Commit()
	if err != nil {
		tx.Rollback()
	}
	return
}

func rawInsertUser(tx *sql.Tx) (err error) {
	_, err = tx.Exec(`INSERT INTO users (username, name, status, email) VALUES (?, ?, ?, ?)`,
		testUser.Username, testUser.Name, testUser.Status, testUser.Email)
	return
}

func rawInsertConnector(tx *sql.Tx) (err error) {
	_, err = tx.Exec(`INSERT INTO connectors (connectorID, name) VALUES (?, ?)`,
		testConnector.ConnectorID, testConnector.Name)
	return
}

func rawInsertFirstDevice(tx *sql.Tx) (err error) {
	_, err = tx.Exec(`INSERT INTO devices (deviceID, name, connectorID) VALUES (?, ?, ?)`,
		testFirstDevice.DeviceID, testFirstDevice.Name, testFirstDevice.ConnectorID)
	return
}

func rawInsertSecondDevice(tx *sql.Tx) (err error) {
	_, err = tx.Exec(`INSERT INTO devices (deviceID, name, connectorID) VALUES (?, ?, ?)`,
		testSecondDevice.DeviceID, testSecondDevice.Name, testSecondDevice.ConnectorID)
	return
}

func TestSelectOne(t *testing.T) {
	var (
		err      error
		estimate = testUser
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertUser,
	)

	var r = User{
		Username: estimate.Username,
	}

	found, err := db.One(&r)
	if err != nil {
		t.Fatal(err)
	}

	if !found {
		t.Fatalf("Object not found\n")
	}

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

type DeviceBrief struct {
	DeviceID  int       `json:"deviceID" qx:"table=devices; pk"`
	Name      string    `json:"name"`
	Connector Connector `json:"connector"`
}

func TestSelectOneWithEmbed(t *testing.T) {
	var (
		err      error
		estimate = testDeviceBrief
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	var r = DeviceBrief{
		DeviceID: testSecondDevice.DeviceID,
	}

	found, err := db.One(&r)
	if err != nil {
		t.Fatal(err)
	}

	if !found {
		t.Fatalf("Object not found\n")
	}

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

func TestSelectOneByWithEmbed(t *testing.T) {
	var (
		err      error
		r        DeviceBrief
		estimate = testDeviceBrief
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	found, err := db.OneBy(&r, "WHERE devices.deviceID=?", testSecondDevice.DeviceID)
	if err != nil {
		t.Fatal(err)
	}

	if !found {
		t.Fatalf("Object not found\n")
	}

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

func TestSelectStructOneQueryWithEmbedAndTwoINs(t *testing.T) {
	var (
		err      error
		r        DeviceBrief
		estimate = testDeviceBrief
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	// ВАЖНО!
	// Тестируем 2 типа IN в одном запросе.
	// Если аргумент - скаляр - IN(?) принимает 1 параметр, если аргумент - массив
	// (из 3 элементов) - IN(?) будет развернут в IN(?, ?, ?)

	found, err := db.OneQuery(&r, `
	SELECT d.deviceID, d.name, c.connectorID, c.name 
	FROM devices d
		INNER JOIN connectors c ON d.connectorID=c.connectorID
	WHERE d.deviceID IN (?) AND c.connectorID IN (?) AND d.name != ?`,
		testSecondDevice.DeviceID, connectorIDs, "some name")
	if err != nil {
		t.Fatal(err)
	}

	if !found {
		t.Fatalf("Object not found\n")
	}

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

func TestSelectScalarOneQueryWithEmbedAndTwoINs(t *testing.T) {
	var (
		err      error
		r        string // device name
		estimate = testSecondDevice.Name
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	// ВАЖНО!
	// Тестируем 2 типа IN в одном запросе.
	// Если аргумент - скаляр - IN(?) принимает 1 параметр, если аргумент - массив
	// (из 3 элементов) - IN(?) будет развернут в IN(?, ?, ?)

	found, err := db.OneQuery(&r, `
	SELECT d.name
	FROM devices d
		INNER JOIN connectors c ON d.connectorID=c.connectorID
	WHERE d.deviceID IN (?) AND c.connectorID IN (?) AND d.name != ?`,
		testSecondDevice.DeviceID, connectorIDs, "some name")
	if err != nil {
		t.Fatal(err)
	}

	if !found {
		t.Fatalf("Object not found\n")
	}

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

// Тестируем выборку списка структур со встроенными объектами.
// В базу записываем 1 объект для простоты. Прочитанный объект сравниваем с ожидаемым.
func TestSelectStructListWithEmbed(t *testing.T) {
	var (
		err      error
		list     []DeviceBrief
		estimate = testDeviceBrief
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertSecondDevice,
	)

	err = db.List(&list)
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != 1 {
		t.Fatalf("Wrong list size - estimate 1 item, got %d\n", len(list))
	}

	r := list[0]

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

// Тестируем выборку списка указателей на структуры со встроенными объектами.
// В базу записываем 1 объект для простоты. Прочитанный объект сравниваем с ожидаемым.
func TestSelectPtrStructListWithEmbed(t *testing.T) {
	var (
		err      error
		list     []*DeviceBrief
		estimate = &testDeviceBrief
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertSecondDevice,
	)

	err = db.List(&list)
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != 1 {
		t.Fatalf("Wrong list size - estimate 1 item, got %d\n", len(list))
	}

	r := list[0]

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

// Тестируем выборку списка структур со встроенными объектами.
// В базу записываем 2 объекта. Запросом достаем 2й объект с использованием
// двух IN. Прочитанный объект сравниваем с ожидаемым.
func TestSelectListByWithEmbedAndTwoINs(t *testing.T) {
	var (
		err      error
		list     []DeviceBrief
		estimate = testDeviceBrief
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	err = db.ListBy(&list,
		"WHERE devices.deviceID IN (?) AND connectors.connectorID IN (?) AND devices.name != ?",
		testSecondDevice.DeviceID, connectorIDs, "some name")
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != 1 {
		t.Fatalf("Wrong list size - estimate 1 item, got %d\n", len(list))
	}

	r := list[0]

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

// Тестируем выборку списка структур со встроенными объектами по полному запросу.
// В базу записываем 2 объекта. Запросом достаем 2й объект с использованием
// двух IN. Прочитанный объект сравниваем с ожидаемым.
func TestSelectStructListQueryWithEmbedAndTwoINs(t *testing.T) {
	var (
		err      error
		list     []DeviceBrief
		estimate = testDeviceBrief
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	// ВАЖНО!
	// Тестируем 2 типа IN в одном запросе.
	// Если аргумент - скаляр - IN(?) принимает 1 параметр, если аргумент - массив
	// (из 3 элементов) - IN(?) будет развернут в IN(?, ?, ?)

	err = db.ListQuery(&list, `
	SELECT d.deviceID, d.name, c.connectorID, c.name 
	FROM devices d
		INNER JOIN connectors c ON d.connectorID=c.connectorID
	WHERE d.deviceID IN (?) AND c.connectorID IN (?) AND d.name != ?`,
		testSecondDevice.DeviceID, connectorIDs, "some name")
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != 1 {
		t.Fatalf("Wrong list size - estimate 1 item, got %d\n", len(list))
	}

	r := list[0]

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

// Тестируем выборку списка простых значений.
// В базу записываем 2 объекта. Запросом достаем имя второго объекта с использованием
// двух IN. Имя сравниваем с ожидаемым.
func TestSelectValuesListWithTwoINs(t *testing.T) {
	var (
		err      error
		list     []string
		estimate = testSecondDevice.Name
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	err = db.ListQuery(&list, `
	SELECT name FROM devices WHERE deviceID IN (?) AND connectorID IN (?) AND name != ?`,
		testSecondDevice.DeviceID, connectorIDs, "some name")
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != 1 {
		t.Fatalf("Wrong list size - estimate 1 item, got %d\n", len(list))
	}

	r := list[0]

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

func TestSelectPtrValuesListWithTwoINs(t *testing.T) {
	var (
		err      error
		list     []*string
		estimate = &testSecondDevice.Name
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	err = db.ListQuery(&list, `
	SELECT name FROM devices WHERE deviceID IN (?) AND connectorID IN (?) AND name != ?`,
		testSecondDevice.DeviceID, connectorIDs, "some name")
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != 1 {
		t.Fatalf("Wrong list size - estimate 1 item, got %d\n", len(list))
	}

	r := list[0]

	if !reflect.DeepEqual(estimate, r) {
		t.Fatalf("\nEstimate: %# v\nSelected: %# v\n",
			pretty.Formatter(estimate), pretty.Formatter(r))
	}
}

func TestHasWithTwoINs(t *testing.T) {
	var (
		err error
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
		rawInsertFirstDevice,
		rawInsertSecondDevice,
	)

	found, err := db.Has("devices",
		`WHERE deviceID IN (?) AND connectorID IN (?) AND name != ?`,
		testSecondDevice.DeviceID, connectorIDs, "some name")
	if err != nil {
		t.Fatal(err)
	}

	if !found {
		t.Fatalf("Object not found")
	}
}

// EXEC

// Удаляем элемент
func TestExecWithIN(t *testing.T) {
	var (
		err error
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
	)

	err = db.Exec("DELETE FROM connectors WHERE connectorID IN (?) AND name != ?",
		connectorIDs, "some name")
	if err != nil {
		t.Fatal(err)
	}

	var qty int
	err = rawdb.QueryRow(`SELECT COUNT(connectorID) FROM connectors`).Scan(&qty)
	if err != nil {
		return
	}

	if qty != 0 {
		t.Fatal("Object not removed\n")
	}
}

func TestExecInsert(t *testing.T) {
	var (
		err        error
		id         int64
		name       string
		originName = "xxx"
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	id, err = db.ExecInsert("", "INSERT INTO connectors (name) VALUES (?)", name)
	if err != nil {
		t.Fatal(err)
	}

	if id == 0 {
		t.Fatalf("Wrong lastInsertID: %d\n", id)
	}

	err = rawdb.QueryRow(`SELECT name FROM connectors WHERE connectorID`,
		id).Scan(&name)
	if err != nil {
		return
	}

	if name != originName {
		t.Fatalf("Name are differs: origin=%q, got=%q\n", originName, name)
	}
}

func TestPrepareInsert(t *testing.T) {
	var (
		err        error
		id         int64
		name       string
		originName = "xxx"
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	tx, err := db.Tx()
	if err != nil {
		t.Fatal(err)
	}
	defer tx.Rollback()

	stmt, err := tx.PrepareInsert("", "INSERT INTO connectors (name) VALUES (?)")
	if err != nil {
		t.Fatal(err)
	}

	id, err = stmt.Insert(name)
	if err != nil {
		t.Fatal(err)
	}

	if id == 0 {
		t.Fatalf("Wrong lastInsertID: %d\n", id)
	}

	err = rawdb.QueryRow(`SELECT name FROM connectors WHERE connectorID`,
		id).Scan(&name)
	if err != nil {
		return
	}

	if name != originName {
		t.Fatalf("Name are differs: origin=%q, got=%q\n", originName, name)
	}
}

// Добавляем запись в БД, а Prepared запросом удаляем запись. Используем IN в запросе.
//
func TestPrepareWithIN(t *testing.T) {
	var (
		err error
	)

	err = clear()
	if err != nil {
		t.Fatal(err)
	}

	rawInsert(
		rawInsertConnector,
	)

	tx, err := db.Tx()
	if err != nil {
		t.Fatal(err)
	}
	defer tx.Close(&err)

	stmt, err := tx.Prepare("DELETE FROM connectors WHERE connectorID IN (?) AND name != ?")
	if err != nil {
		t.Fatal(err)
	}

	err = stmt.Exec(testConnector.ConnectorID, "some name")
	if err != nil {
		t.Fatal(err)
	}

	err = tx.Commit()
	if err != nil {
		t.Fatal(err)
	}

	var qty int
	err = rawdb.QueryRow(`SELECT COUNT(connectorID) FROM connectors`).Scan(&qty)
	if err != nil {
		return
	}

	if qty != 0 {
		t.Fatal("Object not removed\n")
	}
}

//	fmt.Printf("name: %s\n\n", r)
// Тестировать нужно findOne и findList, а также buildSelectStruct

//fmt.Printf("\nResult: %# v\n", pretty.Formatter(x))

/*
	// Читаем данные из БД
	r, err := getModem()
	if err != nil {
		t.Fatal(err)
	}


*/
