package enterprise

import (
	"context"
	"fmt"

	"github.com/influxdata/influxdb/v2/chronograf"
)

// UserStore uses a control client operate on Influx Enterprise users
type UserStore struct {
	Ctrl
	Logger chronograf.Logger
}

// Add creates a new User in Influx Enterprise
func (c *UserStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
	if err := c.Ctrl.CreateUser(ctx, u.Name, u.Passwd); err != nil {
		return nil, err
	}
	perms := ToEnterprise(u.Permissions)

	if err := c.Ctrl.SetUserPerms(ctx, u.Name, perms); err != nil {
		return nil, err
	}
	for _, role := range u.Roles {
		if err := c.Ctrl.AddRoleUsers(ctx, role.Name, []string{u.Name}); err != nil {
			return nil, err
		}
	}

	return c.Get(ctx, chronograf.UserQuery{Name: &u.Name})
}

// Delete the User from Influx Enterprise
func (c *UserStore) Delete(ctx context.Context, u *chronograf.User) error {
	return c.Ctrl.DeleteUser(ctx, u.Name)
}

// Num of users in Influx
func (c *UserStore) Num(ctx context.Context) (int, error) {
	all, err := c.All(ctx)
	if err != nil {
		return 0, err
	}

	return len(all), nil
}

// Get retrieves a user if name exists.
func (c *UserStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
	if q.Name == nil {
		return nil, fmt.Errorf("query must specify name")
	}
	u, err := c.Ctrl.User(ctx, *q.Name)
	if err != nil {
		return nil, err
	}

	ur, err := c.Ctrl.UserRoles(ctx)
	if err != nil {
		return nil, err
	}

	role := ur[*q.Name]
	cr := role.ToChronograf()
	// For now we are removing all users from a role being returned.
	for i, r := range cr {
		r.Users = []chronograf.User{}
		cr[i] = r
	}
	return &chronograf.User{
		Name:        u.Name,
		Permissions: ToChronograf(u.Permissions),
		Roles:       cr,
	}, nil
}

// Update the user's permissions or roles
func (c *UserStore) Update(ctx context.Context, u *chronograf.User) error {
	// Only allow one type of change at a time. If it is a password
	// change then do it and return without any changes to permissions
	if u.Passwd != "" {
		return c.Ctrl.ChangePassword(ctx, u.Name, u.Passwd)
	}

	if u.Roles != nil {
		// Make a list of the roles we want this user to have:
		want := make([]string, len(u.Roles))
		for i, r := range u.Roles {
			want[i] = r.Name
		}

		// Find the list of all roles this user is currently in
		userRoles, err := c.UserRoles(ctx)
		if err != nil {
			return nil
		}
		// Make a list of the roles the user currently has
		roles := userRoles[u.Name]
		have := make([]string, len(roles.Roles))
		for i, r := range roles.Roles {
			have[i] = r.Name
		}

		// Calculate the roles the user will be removed from and the roles the user
		// will be added to.
		revoke, add := Difference(want, have)

		// First, add the user to the new roles
		for _, role := range add {
			if err := c.Ctrl.AddRoleUsers(ctx, role, []string{u.Name}); err != nil {
				return err
			}
		}

		// ... and now remove the user from an extra roles
		for _, role := range revoke {
			if err := c.Ctrl.RemoveRoleUsers(ctx, role, []string{u.Name}); err != nil {
				return err
			}
		}
	}

	if u.Permissions != nil {
		perms := ToEnterprise(u.Permissions)
		return c.Ctrl.SetUserPerms(ctx, u.Name, perms)
	}
	return nil
}

// All is all users in influx
func (c *UserStore) All(ctx context.Context) ([]chronograf.User, error) {
	all, err := c.Ctrl.Users(ctx, nil)
	if err != nil {
		return nil, err
	}

	ur, err := c.Ctrl.UserRoles(ctx)
	if err != nil {
		return nil, err
	}

	res := make([]chronograf.User, len(all.Users))
	for i, user := range all.Users {
		role := ur[user.Name]
		cr := role.ToChronograf()
		// For now we are removing all users from a role being returned.
		for i, r := range cr {
			r.Users = []chronograf.User{}
			cr[i] = r
		}

		res[i] = chronograf.User{
			Name:        user.Name,
			Permissions: ToChronograf(user.Permissions),
			Roles:       cr,
		}
	}
	return res, nil
}

// ToEnterprise converts chronograf permission shape to enterprise
func ToEnterprise(perms chronograf.Permissions) Permissions {
	res := Permissions{}
	for _, perm := range perms {
		if perm.Scope == chronograf.AllScope {
			// Enterprise uses empty string as the key for all databases
			res[""] = perm.Allowed
		} else {
			res[perm.Name] = perm.Allowed
		}
	}
	return res
}

// ToChronograf converts enterprise permissions shape to chronograf shape
func ToChronograf(perms Permissions) chronograf.Permissions {
	res := chronograf.Permissions{}
	for db, perm := range perms {
		// Enterprise uses empty string as the key for all databases
		if db == "" {
			res = append(res, chronograf.Permission{
				Scope:   chronograf.AllScope,
				Allowed: perm,
			})
		} else {
			res = append(res, chronograf.Permission{
				Scope:   chronograf.DBScope,
				Name:    db,
				Allowed: perm,
			})

		}
	}
	return res
}
