package util

import (
	"database/sql"
	"fmt"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/schrodinger-test/pkg/dbutil"
	"github.com/pingcap/schrodinger/pkg/types"
)

// GetRandID gets rand id in table
func GetRandID(db *sql.DB, schema, tableName string) (int64, error) {
	sql := fmt.Sprintf("SELECT id FROM `%s`.`%s` ORDER BY RAND() LIMIT 1", schema, tableName)
	rows, err := db.Query(sql)
	if err != nil {
		return 0, errors.Trace(err)
	}
	defer rows.Close()

	var (
		id int64
	)

	for rows.Next() {
		if err = rows.Scan(&id); err != nil {
			return 0, errors.Trace(err)
		}
	}
	return id, nil
}

// GetAllIDs gets all ids in table
func GetAllIDs(db *sql.DB, schema, tableName string) ([]int64, error) {
	sql := fmt.Sprintf("SELECT id FROM `%s`.`%s`", schema, tableName)
	rows, err := db.Query(sql)
	if err != nil {
		return nil, errors.Trace(err)
	}

	defer rows.Close()

	var (
		id int64
	)
	var ids []int64
	for rows.Next() {
		if err = rows.Scan(&id); err != nil {
			return nil, errors.Trace(err)
		}
		ids = append(ids, id)
	}

	if err := rows.Err(); err != nil {
		return nil, errors.Trace(err)
	}

	return ids, nil
}

// GetRow gets row
func GetRow(db *sql.DB, schema, tableName string, id int64) (map[string]string, error) {
	sql := fmt.Sprintf("SELECT * FROM `%s`.`%s` WHERE id = %d", schema, tableName, id)
	rows, err := db.Query(sql)
	if err != nil {
		return nil, errors.Trace(err)
	}

	defer rows.Close()

	for rows.Next() {
		fields, _, err := dbutil.ScanRow(rows)
		if err != nil {
			return nil, errors.Trace(err)
		}

		result := make(map[string]string)
		for k, v := range fields {
			result[k] = string(v)
		}

		return result, nil
	}
	return nil, errors.NotFoundf("table %s id %d", tableName, id)
}

// GetTidbVersion gets tidb version
func GetTidbVersion(db *sql.DB) (string, error) {
	sql := "select tidb_version();"
	rows, err := db.Query(sql)
	if err != nil {
		return "", errors.Trace(err)
	}

	defer rows.Close()

	var (
		version string
	)

	for rows.Next() {
		if err = rows.Scan(&version); err != nil {
			return "", errors.Trace(err)
		}
		version = strings.Split(version, "\n")[0]
		version = strings.Trim(strings.Split(version, ":")[1], " ")
		return version, nil
	}

	if err := rows.Err(); err != nil {
		return "", errors.Trace(err)
	}

	return "", errors.NotFoundf("version")
}

// Meta is meta data
type Meta struct {
	BinLogName string
	BinLogPos  uint32
}

// GetMasterPos gets master pos
func GetMasterPos(db *sql.DB, typ types.Type) (*Meta, error) {
	var (
		binlogName string
		pos        uint32
		nullPtr    interface{}
		err        error
	)
	if typ == types.MysqlType {
		err = db.QueryRow(`SHOW MASTER STATUS`).Scan(&binlogName, &pos, &nullPtr, &nullPtr, &nullPtr)
	} else {
		err = db.QueryRow(`SHOW MASTER STATUS`).Scan(&binlogName, &pos, &nullPtr, &nullPtr)
	}
	if err != nil {
		return nil, errors.Trace(err)
	}

	binlogPos := &Meta{
		BinLogName: binlogName,
		BinLogPos:  pos,
	}
	return binlogPos, nil
}

// GetColumnInfo returns column information of `show columns from`
func GetColumnInfo(db *sql.DB, schema, table, field string) (map[string]string, error) {
	stmt := fmt.Sprintf("show columns from `%s`.`%s` where Field = ?", schema, table)

	rows, err := db.Query(stmt, field)
	if err != nil {
		return nil, errors.Trace(err)
	}
	defer rows.Close()

	for rows.Next() {
		fields, _, err := dbutil.ScanRow(rows)
		if err != nil {
			return nil, errors.Trace(err)
		}

		result := make(map[string]string)
		for k, v := range fields {
			result[k] = string(v)
		}

		return result, nil
	}
	return nil, errors.NotFoundf("table:`%s`.`%s` field:%s", schema, table, field)

}
