package dbctrl

import (
	"context"
	"database/sql"
	"errors"
	"fmt"

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

type DBAddress struct {
	Host     string
	Port     int
	Database string
	Account  string
	Password string
}

func (dbAddr *DBAddress) IsValid() bool {
	return isValidPort(dbAddr.Port) && len(dbAddr.Host) != 0 && len(dbAddr.Database) != 0 &&
		len(dbAddr.Account) != 0 && 0 != len(dbAddr.Password)
}

type DBConnPool struct {
	DBAddress

	db *sql.DB
}

const DefaultTiDBPort = 4000

func isValidPort(port int) bool {
	return port >= 1024 && port <= 65535
}

func NewDBConnInfoByAdd(dbAddr *DBAddress) (*DBConnPool, error) {
	if !dbAddr.IsValid() {
		return nil, errors.New("invalid DB connection address")
	}

	return &DBConnPool{*dbAddr, nil}, nil
}

func NewDBConnInfo(host string, port int, database string,
	account string, password string) (*DBConnPool, error) {

	if len(host) == 0 {
		host = "localhost"
	}

	if !isValidPort(port) {
		port = DefaultTiDBPort
	}

	if len(database) == 0 {
		database = "mysql"
	}

	if len(account) == 0 || len(password) == 0 {
		return nil, errors.New("account/Password must be provided")
	}

	return &DBConnPool{
		DBAddress{
			Host:     host,
			Port:     port,
			Database: database,
			Account:  account,
			Password: password,
		},
		nil,
	}, nil
}

func (dbcp *DBConnPool) url() (string, error) {
	if len(dbcp.Host) == 0 ||
		len(dbcp.Database) == 0 ||
		len(dbcp.Account) == 0 ||
		!isValidPort(dbcp.Port) {
		return "", errors.New("invalid DB address")
	}

	return fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true&loc=Local",
		dbcp.Account, dbcp.Password, dbcp.Host, dbcp.Port, dbcp.Database), nil
}

func (dbcp *DBConnPool) Connect() (db *sql.DB, err error) {
	if dbcp.db != nil {
		return dbcp.db, nil
	}

	url, err := dbcp.url()
	if err != nil || url == "" {
		return nil, err
	}

	dbcp.db, err = sql.Open("mysql", url)
	return dbcp.db, err
}

func (dbcp *DBConnPool) Close() error {
	return dbcp.db.Close()
}

func (dbcp *DBConnPool) Query(stmt string) (*sql.Rows, error) {
	_, err := dbcp.Connect()
	if err != nil {
		return nil, err
	}

	var ctx context.Context
	return dbcp.db.QueryContext(ctx, stmt)
}

func (dbcp *DBConnPool) Execute(stmt string) (sql.Result, error) {
	_, err := dbcp.Connect()
	if err != nil {
		return nil, err
	}

	var ctx context.Context
	return dbcp.db.ExecContext(ctx, stmt)
}
