package clisetup

import (
	"context"
	"fmt"
	"os"

	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/args"
	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clisetup/setup"
	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/reload"
	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/core/require"
	"github.com/crowdsecurity/crowdsec/pkg/hubops"
)

func (cli *cliSetup) newInstallHubCmd() *cobra.Command {
	var (
		interactive bool
		dryRun      bool
	)

	cmd := &cobra.Command{
		Use:   "install-hub [setup_file] [flags]",
		Short: "Install recommended hub items from a setup file",
		Long: `Install the CrowdSec hub items (collections, scenarios, etc.)
recommended for each detected service, based on a setup file.

This command reads a setup file (typically generated by 'cscli setup detect')
and then installs all required hub content to support the detected services.`,
		Example: `# detect running services, create a setup file
cscli setup detect > setup.yaml

# install collection, etc.
cscli setup install-hub setup.yaml

# dry-run to preview what would be installed
cscli setup install-hub setup.yaml --dry-run
`,
		Args:              args.ExactArgs(1),
		DisableAutoGenTag: true,
		RunE: func(cmd *cobra.Command, args []string) error {
			inputReader, err := maybeStdinFile(args[0])
			if err != nil {
				return err
			}

			stup, err := setup.ParseSetupYAML(inputReader, true, cli.cfg().Cscli.Color != "no")
			if err != nil {
				return err
			}

			logger := logrus.StandardLogger()

			plan, err := cli.install(cmd.Context(), interactive, dryRun, stup.CollectHubSpecs(), logger)
			if err != nil {
				return err
			}

			if msg := reload.UserMessage(); msg != "" && plan.ReloadNeeded {
				fmt.Fprintln(os.Stdout, "\n"+msg)
			}

			return nil
		},
	}

	flags := cmd.Flags()
	flags.BoolVarP(&interactive, "interactive", "i", false, "Ask for confirmation before proceeding")
	flags.BoolVar(&dryRun, "dry-run", false, "simulate the installation without making any changes")
	cmd.MarkFlagsMutuallyExclusive("interactive", "dry-run")

	return cmd
}

func (cli *cliSetup) install(ctx context.Context, interactive bool, dryRun bool, hubSpecs []setup.HubSpec, logger logrus.FieldLogger) (*hubops.ActionPlan, error) {
	cfg := cli.cfg()

	hub, err := require.Hub(cfg, logrus.StandardLogger())
	if err != nil {
		return nil, err
	}

	contentProvider, err := require.HubDownloader(ctx, cfg)
	if err != nil {
		return nil, err
	}

	showPlan := interactive
	// in dry-run, it can be useful to see the _order_ in which files are installed.
	verbosePlan := dryRun

	plan := hubops.NewActionPlan(hub)

	for _, itemMap := range hubSpecs {
		for itemType, itemNames := range itemMap {
			for _, itemName := range itemNames {
				fqName := itemType + ":" + itemName

				item, err := hub.GetItemFQ(fqName)
				if err != nil {
					logger.Errorf("Could not find %q in hub index, skipping install", fqName)
					continue
				}

				if err := plan.AddCommand(hubops.NewDownloadCommand(item, contentProvider, false)); err != nil {
					return nil, err
				}

				if err := plan.AddCommand(hubops.NewEnableCommand(item, false)); err != nil {
					return nil, err
				}
			}
		}
	}

	err = plan.Execute(ctx, interactive, dryRun, showPlan, verbosePlan)
	if err != nil {
		return nil, err
	}

	return plan, nil
}
