package engine

import (
	"context"
	"fmt"
	"strings"

	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/cli/command/formatter"
	"github.com/docker/cli/internal/licenseutils"
	clitypes "github.com/docker/cli/types"
	"github.com/docker/docker/api/types"
	"github.com/docker/licensing/model"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

type activateOptions struct {
	licenseFile      string
	version          string
	registryPrefix   string
	format           string
	image            string
	quiet            bool
	displayOnly      bool
	sockPath         string
	licenseLoginFunc func(ctx context.Context, authConfig *types.AuthConfig) (licenseutils.HubUser, error)
}

// newActivateCommand creates a new `docker engine activate` command
func newActivateCommand(dockerCli command.Cli) *cobra.Command {
	var options activateOptions
	options.licenseLoginFunc = licenseutils.Login

	cmd := &cobra.Command{
		Use:   "activate [OPTIONS]",
		Short: "Activate Enterprise Edition",
		Long: `Activate Enterprise Edition.

With this command you may apply an existing Docker enterprise license, or
interactively download one from Docker. In the interactive exchange, you can
sign up for a new trial, or download an existing license. If you are
currently running a Community Edition engine, the daemon will be updated to
the Enterprise Edition Docker engine with additional capabilities and long
term support.

For more information about different Docker Enterprise license types visit
https://www.docker.com/licenses

For non-interactive scriptable deployments, download your license from
https://hub.docker.com/ then specify the file with the '--license' flag.
`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return runActivate(dockerCli, options)
		},
	}

	flags := cmd.Flags()

	flags.StringVar(&options.licenseFile, "license", "", "License File")
	flags.StringVar(&options.version, "version", "", "Specify engine version (default is to use currently running version)")
	flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the default location where engine images are pulled")
	flags.StringVar(&options.image, "engine-image", "", "Specify engine image")
	flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template")
	flags.BoolVar(&options.displayOnly, "display-only", false, "only display license information and exit")
	flags.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID")
	flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")

	return cmd
}

func runActivate(cli command.Cli, options activateOptions) error {
	if !isRoot() {
		return errors.New("this command must be run as a privileged user")
	}
	ctx := context.Background()
	client, err := cli.NewContainerizedEngineClient(options.sockPath)
	if err != nil {
		return errors.Wrap(err, "unable to access local containerd")
	}
	defer client.Close()

	authConfig, err := getRegistryAuth(cli, options.registryPrefix)
	if err != nil {
		return err
	}

	var license *model.IssuedLicense

	// Lookup on hub if no license provided via params
	if options.licenseFile == "" {
		if license, err = getLicenses(ctx, authConfig, cli, options); err != nil {
			return err
		}
		if options.displayOnly {
			return nil
		}
	} else {
		if license, err = licenseutils.LoadLocalIssuedLicense(ctx, options.licenseFile); err != nil {
			return err
		}
	}
	summary, err := licenseutils.GetLicenseSummary(ctx, *license)
	if err != nil {
		return err
	}
	fmt.Fprintf(cli.Out(), "License: %s\n", summary)
	if options.displayOnly {
		return nil
	}
	dclient := cli.Client()
	if err = licenseutils.ApplyLicense(ctx, dclient, license); err != nil {
		return err
	}

	// Short circuit if the user didn't specify a version and we're already running enterprise
	if options.version == "" {
		serverVersion, err := dclient.ServerVersion(ctx)
		if err != nil {
			return err
		}
		if strings.Contains(strings.ToLower(serverVersion.Platform.Name), "enterprise") {
			fmt.Fprintln(cli.Out(), "Successfully activated engine license on existing enterprise engine.")
			return nil
		}
		options.version = serverVersion.Version
	}

	opts := clitypes.EngineInitOptions{
		RegistryPrefix: options.registryPrefix,
		EngineImage:    options.image,
		EngineVersion:  options.version,
	}

	if err := client.ActivateEngine(ctx, opts, cli.Out(), authConfig); err != nil {
		return err
	}
	fmt.Fprintln(cli.Out(), `Successfully activated engine.
Restart docker with 'systemctl restart docker' to complete the activation.`)
	return nil
}

func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) {
	user, err := options.licenseLoginFunc(ctx, authConfig)
	if err != nil {
		return nil, err
	}
	fmt.Fprintf(cli.Out(), "Looking for existing licenses for %s...\n", user.User.Username)
	subs, err := user.GetAvailableLicenses(ctx)
	if err != nil {
		return nil, err
	}
	if len(subs) == 0 {
		return doTrialFlow(ctx, cli, user)
	}

	format := options.format
	if len(format) == 0 {
		format = formatter.TableFormatKey
	}

	updatesCtx := formatter.Context{
		Output: cli.Out(),
		Format: NewSubscriptionsFormat(format, options.quiet),
		Trunc:  false,
	}
	if err := SubscriptionsWrite(updatesCtx, subs); err != nil {
		return nil, err
	}
	if options.displayOnly {
		return nil, nil
	}
	fmt.Fprintf(cli.Out(), "Please pick a license by number: ")
	var num int
	if _, err := fmt.Fscan(cli.In(), &num); err != nil {
		return nil, errors.Wrap(err, "failed to read user input")
	}
	if num < 0 || num >= len(subs) {
		return nil, fmt.Errorf("invalid choice")
	}
	return user.GetIssuedLicense(ctx, subs[num].ID)
}

func doTrialFlow(ctx context.Context, cli command.Cli, user licenseutils.HubUser) (*model.IssuedLicense, error) {
	if !command.PromptForConfirmation(cli.In(), cli.Out(),
		"No existing licenses found, would you like to set up a new Enterprise Basic Trial license?") {
		return nil, fmt.Errorf("you must have an existing enterprise license or generate a new trial to use the Enterprise Docker Engine")
	}
	targetID := user.User.ID
	// If the user is a member of any organizations, allow trials generated against them
	if len(user.Orgs) > 0 {
		fmt.Fprintf(cli.Out(), "%d\t%s\n", 0, user.User.Username)
		for i, org := range user.Orgs {
			fmt.Fprintf(cli.Out(), "%d\t%s\n", i+1, org.Orgname)
		}
		fmt.Fprintf(cli.Out(), "Please choose an account to generate the trial in:")
		var num int
		if _, err := fmt.Fscan(cli.In(), &num); err != nil {
			return nil, errors.Wrap(err, "failed to read user input")
		}
		if num < 0 || num > len(user.Orgs) {
			return nil, fmt.Errorf("invalid choice")
		}
		if num > 0 {
			targetID = user.Orgs[num-1].ID
		}
	}
	return user.GenerateTrialLicense(ctx, targetID)
}
