package service

import (
	"database/sql"

	"gorm.io/gorm"
)

// NOTE: all users/roles are omitted host info(e.g. '@%', '@localhost')
const (
	// SqlCreateTokenBasedUser
	// CREATE USER 'user@pingcap.com' IDENTIFIED WITH tidb_auth_token REQUIRE TOKEN_ISSUER 'pingcap.com' ATTRIBUTE '{"email": "user@pingcap.com"}';
	SqlCreateTokenBasedUser = `CREATE USER IF NOT EXISTS @username IDENTIFIED WITH tidb_auth_token REQUIRE TOKEN_ISSUER @token_issuer ATTRIBUTE @attribute;`
	// SqlCreatePasswordBasedUser sql for create an user defined user
	// CREATE USER 'user' IDENTIFIED BY 'password';
	SqlCreatePasswordBasedUser = `CREATE USER IF NOT EXISTS @username IDENTIFIED BY @password;`
	// SqlDropUser sql for drop an user
	// DROP USER 'user';
	SqlDropUser = `DROP USER IF EXISTS @username;`

	// SqlCreateRole
	// CREATE ROLE 'role_readonly';
	SqlCreateRole = `CREATE ROLE IF NOT EXISTS @role_name;`

	// SqlGrantUserRoles sql for grant some roles to user
	// GRANT 'app_read', 'app_write' TO 'rw_user1';
	SqlGrantUserRoles = `GRANT @roles TO @username;`
	SqlGrantUserRole  = `GRANT @role TO @username;`
	// SqlRevokeUserRoles sql for revoke some roles from user
	// REVOKE 'app_read', 'app_write' FROM 'rw_user1';
	SqlRevokeUserRoles = `REVOKE @roles FROM @username;`
	SqlRevokeUserRole  = `REVOKE @role FROM @username;`
	// SqlEnableAllRole sql for enable all role for user
	// SET DEFAULT ROLE ALL TO 'dev1';
	SqlEnableAllRole = `SET DEFAULT ROLE ALL TO @username;`
	// SqlShowRoles
	// show grants for 'rw_user2';
	SqlShowRoles = `SHOW GRANTS FOR @username;`
)

func (s *UserRoleService) buildCreateTokenBasedUserSql(username, attribute, tokenIssuer string) string {
	return s.buildSql(
		SqlCreateTokenBasedUser,
		sql.Named("username", username),
		sql.Named("attribute", attribute),
		sql.Named("token_issuer", tokenIssuer),
	)
}

func (s *UserRoleService) buildCreatePasswordBasedUserSql(username, password string) string {
	return s.buildSql(SqlCreatePasswordBasedUser, sql.Named("username", username), sql.Named("password", password))
}

func (s *UserRoleService) buildDropUserSql(username string) string {
	return s.buildSql(SqlDropUser, sql.Named("username", username))
}

//nolint:unused
func (s *UserRoleService) buildCreateRoleSql(roleName string) string {
	return s.buildSql(SqlCreateRole, sql.Named("role_name", roleName))
}

func (s *UserRoleService) buildGrantUserRolesSql(username string, roles []string) []string {
	if len(roles) == 0 {
		return nil
	}
	// NOTE: since 'GRANT @roles TO @user' with roles=[]string{"role1","role2"}, user="user1"
	// will generate statement "GRANT ('role1','role2') TO 'user1'", but it is invalid.
	// return buildSql(SqlGrantUserRoles, sql.Named("username", username), sql.Named("roles", roles))

	out := make([]string, 0, len(roles))
	for _, role := range roles {
		out = append(out, s.buildSql(SqlGrantUserRole, sql.Named("username", username), sql.Named("role", role)))
	}
	return out
}

//nolint:unused
func (s *UserRoleService) buildRevokeUserRolesSql(username string, roles []string) []string {
	if len(roles) == 0 {
		return nil
	}
	// return buildSql(SqlRevokeUserRoles, sql.Named("username", username), sql.Named("roles", roles))

	out := make([]string, 0, len(roles))
	for _, role := range roles {
		out = append(out, s.buildSql(SqlRevokeUserRole, sql.Named("username", username), sql.Named("role", role)))
	}
	return out
}

func (s *UserRoleService) buildEnableAllRoleSql(username string) string {
	return s.buildSql(SqlEnableAllRole, sql.Named("username", username))
}

//nolint:unused
func (s *UserRoleService) buildShowRolesSql(username string) string {
	return s.buildSql(SqlShowRoles, sql.Named("username", username))
}

func (s *UserRoleService) buildSql(sqlTemplate string, args ...any) string {
	sqlStr := s.db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Raw(sqlTemplate, args...)
	})
	return sqlStr
}
