package clusterdb

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

	"code.justin.tv/d8a/buddy/lib/store"
)

// MySQLQueriesFactory is a mysql DriverQueriesFactory implementation that uses the mysql version to return the right DriverQueries
type MySQLQueriesFactory struct{}

//BuildDriverQueries will SELECT @@version from the mysql database and return the correct mysql QueriesDriver implementation based on the results
func (factory *MySQLQueriesFactory) BuildDriverQueries(db *sql.DB) (DriverQueries, error) {
	//5.6 largely doesn't support the cool role pair stuff we do in postgres
	//5.7 sorta does
	//8.0 is 1:1

	version, err := getMysqlVersion(db)
	if err != nil {
		return nil, err
	}

	//Right now, there's no reason to implement 5.7 or 8.0 because aurora is 5.6 only but I'm gonna go ahead & build this factory system
	//now since it took like 30s
	if strings.HasPrefix(version, "5.6.") {
		return &MySQL56Queries{}, nil
	}

	//Lowest version most likely to work
	return &MySQL56Queries{}, nil
}

func getMysqlVersion(db *sql.DB) (string, error) {
	var version string
	rows, err := db.Query("SELECT @@version")
	if err != nil {
		return version, err
	}

	defer store.TryClose(rows)

	for rows.Next() {
		err = rows.Scan(&version)
		break
	}

	return version, err
}

// MySQL56Queries is a DriverQueries implementation for mysql 5.6
type MySQL56Queries struct{}

//CanLockUsers returns whether this backing database supports user lock/unlock to prevent logins for a particular user
func (queries *MySQL56Queries) CanLockUsers() bool {
	return false
}

//CanUseRolePairs returns whether this backing database supports HA password rotations with role pairs
func (queries *MySQL56Queries) CanUseRolePairs() bool {
	return false
}

// TestConnection tests the DB connection by sending a SELECT 1, because sometimes the connection process
// itself doesn't error.
func (queries *MySQL56Queries) TestConnection(db *sql.DB) error {
	rows, err := db.Query("SELECT 1")

	if rows != nil {
		store.TryClose(rows)
	}

	return err
}

// AlterPassword modifies the DB user's password in the clusterDB
func (queries *MySQL56Queries) AlterPassword(db *sql.DB, username string, password string) (sql.Result, error) {
	return db.Exec(fmt.Sprintf("SET PASSWORD \"%s\" = PASSWORD(?)", username), password)
}

// SetUserLock sets the DB user to locked or unlocked for logins
func (queries *MySQL56Queries) SetUserLock(db *sql.DB, username string, locked bool) (sql.Result, error) {
	return nil, fmt.Errorf("mySQL 5.6 can't do user locks")
}

// CreateRole makes a new blank DB role in the cluster db
func (queries *MySQL56Queries) CreateRole(db *sql.DB, rolename string) (sql.Result, error) {
	return nil, fmt.Errorf("mySQL 5.6 can't do role pairs")
}

// CreateUser makes a new DB user in the cluster db, with password and an inherited role
func (queries *MySQL56Queries) CreateUser(db *sql.DB, username string, password string, roleName string) (sql.Result, error) {
	return nil, fmt.Errorf("mySQL 5.6 can't do role pairs")
}

// RoleParents returns a list of role inheritance relationships in the clusterDb, as a map
// of role name -> list of member role names
func (queries *MySQL56Queries) RoleParents(db *sql.DB) (*sql.Rows, error) {
	return nil, fmt.Errorf("mySQL 5.6 can't do role pairs")
}

// GrantedRoles returns a list of role names who have grants in the cluster DB
func (queries *MySQL56Queries) GrantedRoles(db *sql.DB) (*sql.Rows, error) {
	return nil, fmt.Errorf("mySQL 5.6 can't do role pairs")
}

// ActiveUsers returns a list of users currently executing a query on the server
func (queries *MySQL56Queries) ActiveUsers(db *sql.DB) (*sql.Rows, error) {
	return nil, fmt.Errorf("mySQL 5.6 can't do role pairs")
}

// ClusterUsers retrieves all users in the cluster DB, as a map of username -> locked/unlocked  If true, unlocked, if false locked
func (queries *MySQL56Queries) ClusterUsers(db *sql.DB) (*sql.Rows, error) {
	return db.Query("SELECT user, TRUE FROM mysql.user WHERE Super_priv='N' AND Host='%'")
}
