package main

import (
	"fmt"
	"slices"

	"github.com/spf13/cobra"

	"github.com/lxc/incus/v6/cmd/incus/color"
	u "github.com/lxc/incus/v6/cmd/incus/usage"
	"github.com/lxc/incus/v6/internal/i18n"
	cli "github.com/lxc/incus/v6/shared/cmd"
)

type cmdClusterRole struct {
	global  *cmdGlobal
	cluster *cmdCluster
}

// Command returns a cobra.Command for use with (*cobra.Command).AddCommand.
func (c *cmdClusterRole) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = cli.U("role")
	cmd.Short = i18n.G("Manage cluster roles")
	cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Manage cluster roles`))

	// Add
	clusterRoleAddCmd := cmdClusterRoleAdd{global: c.global, cluster: c.cluster, clusterRole: c}
	cmd.AddCommand(clusterRoleAddCmd.Command())

	// Remove
	clusterRoleRemoveCmd := cmdClusterRoleRemove{global: c.global, cluster: c.cluster, clusterRole: c}
	cmd.AddCommand(clusterRoleRemoveCmd.Command())

	// Workaround for subcommand usage errors. See: https://github.com/spf13/cobra/issues/706
	cmd.Args = cobra.NoArgs
	cmd.Run = func(cmd *cobra.Command, _ []string) { _ = cmd.Usage() }
	return cmd
}

type cmdClusterRoleAdd struct {
	global      *cmdGlobal
	cluster     *cmdCluster
	clusterRole *cmdClusterRole
}

var cmdClusterRoleAddUsage = u.Usage{u.Member.Remote(), u.Role.List(1, ",")}

// Command returns a cobra.Command for use with (*cobra.Command).AddCommand.
func (c *cmdClusterRoleAdd) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = cli.U("add", cmdClusterRoleAddUsage...)
	cmd.Aliases = []string{"create"}
	cmd.Short = i18n.G("Add roles to a cluster member")
	cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Add roles to a cluster member`))

	cmd.RunE = c.Run

	cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) == 0 {
			return c.global.cmpClusterMembers(toComplete)
		}

		return nil, cobra.ShellCompDirectiveNoFileComp
	}

	return cmd
}

// Run runs the actual command logic.
func (c *cmdClusterRoleAdd) Run(cmd *cobra.Command, args []string) error {
	parsed, err := cmdClusterRoleAddUsage.Parse(c.global.conf, cmd, args)
	if err != nil {
		return err
	}

	d := parsed[0].RemoteServer
	memberName := parsed[0].RemoteObject.String
	newRoles := parsed[1].StringList

	// Extract the current value
	member, etag, err := d.GetClusterMember(memberName)
	if err != nil {
		return err
	}

	memberWritable := member.Writable()
	for _, newRole := range newRoles {
		if slices.Contains(memberWritable.Roles, newRole) {
			return fmt.Errorf(i18n.G("Member %q already has role %q"), formatRemote(c.global.conf, parsed[0]), newRole)
		}
	}

	memberWritable.Roles = append(memberWritable.Roles, newRoles...)

	return d.UpdateClusterMember(memberName, memberWritable, etag)
}

type cmdClusterRoleRemove struct {
	global      *cmdGlobal
	cluster     *cmdCluster
	clusterRole *cmdClusterRole
}

var cmdClusterRoleRemoteUsage = u.Usage{u.Member.Remote(), u.Role.List(1, ",")}

// Command returns a cobra.Command for use with (*cobra.Command).AddCommand.
func (c *cmdClusterRoleRemove) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = cli.U("remove", cmdClusterRoleRemoteUsage...)
	cmd.Aliases = []string{"delete", "rm"}
	cmd.Short = i18n.G("Remove roles from a cluster member")
	cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
		`Remove roles from a cluster member`))

	cmd.RunE = c.Run

	cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) == 0 {
			return c.global.cmpClusterMembers(toComplete)
		}

		if len(args) == 1 {
			return c.global.cmpClusterMemberRoles(args[0])
		}

		return nil, cobra.ShellCompDirectiveNoFileComp
	}

	return cmd
}

// Run runs the actual command logic.
func (c *cmdClusterRoleRemove) Run(cmd *cobra.Command, args []string) error {
	parsed, err := cmdClusterRoleRemoteUsage.Parse(c.global.conf, cmd, args)
	if err != nil {
		return err
	}

	d := parsed[0].RemoteServer
	memberName := parsed[0].RemoteObject.String
	rolesToRemove := parsed[1].StringList

	// Extract the current value
	member, etag, err := d.GetClusterMember(memberName)
	if err != nil {
		return err
	}

	memberWritable := member.Writable()
	for _, roleToRemove := range rolesToRemove {
		if !slices.Contains(memberWritable.Roles, roleToRemove) {
			return fmt.Errorf(i18n.G("Member %q does not have role %q"), formatRemote(c.global.conf, parsed[0]), roleToRemove)
		}
	}

	memberWritable.Roles = removeElementsFromSlice(memberWritable.Roles, rolesToRemove...)

	return d.UpdateClusterMember(memberName, memberWritable, etag)
}
