// Copyright 2020 The Operator-SDK Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package packagemanifests

import (
	"fmt"
	"path/filepath"

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

	kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder"
	"github.com/operator-framework/operator-sdk/internal/util/projutil"
)

//nolint:maligned
type packagemanifestsCmd struct {
	// Common options.
	operatorName string
	version      string
	fromVersion  string
	inputDir     string
	outputDir    string
	kustomizeDir string
	deployDir    string
	crdsDir      string
	updateCRDs   bool
	stdout       bool
	quiet        bool

	// Package manifest options.
	channelName      string
	isDefaultChannel bool
}

//nolint:maligned
type packagemanifestsCmdLegacy struct {
	packagemanifestsCmd

	apisDir          string
	interactiveLevel projutil.InteractiveLevel
	interactive      bool
}

// NewCmd returns the 'packagemanifests' command configured for the new project layout.
func NewCmd() *cobra.Command {
	c := &packagemanifestsCmd{}

	cmd := &cobra.Command{
		Use:     "packagemanifests",
		Short:   "Generates package manifests data for the operator",
		Long:    longHelp,
		Example: examples,
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) != 0 {
				return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath())
			}

			cfg, err := kbutil.ReadConfig()
			if err != nil {
				log.Fatal(fmt.Errorf("error reading configuration: %v", err))
			}
			c.setDefaults(cfg)

			if err = c.validate(); err != nil {
				return fmt.Errorf("invalid command options: %v", err)
			}
			if err = c.run(cfg); err != nil {
				log.Fatalf("Error generating package manifests: %v", err)
			}

			return nil
		},
	}

	cmd.Flags().StringVar(&c.kustomizeDir, "kustomize-dir", filepath.Join("config", "manifests"),
		"Directory containing kustomize bases and a kustomization.yaml for operator-framework manifests")
	cmd.Flags().BoolVar(&c.stdout, "stdout", false, "Write package to stdout")

	c.addCommonFlagsTo(cmd.Flags())

	return cmd
}

// NewCmdLegacy returns the 'packagemanifests' command configured for the legacy project layout.
func NewCmdLegacy() *cobra.Command {
	c := &packagemanifestsCmdLegacy{}

	cmd := &cobra.Command{
		Use:     "packagemanifests",
		Short:   "Generates a package manifests format",
		Long:    longHelpLegacy,
		Example: examplesLegacy,
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) != 0 {
				return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath())
			}

			// Check if the user has any specific preference to enable/disable interactive prompts.
			// Default behaviour is to disable the prompt unless a base package does not exist.
			if cmd.Flags().Changed("interactive") {
				if c.interactive {
					c.interactiveLevel = projutil.InteractiveOnAll
				} else {
					c.interactiveLevel = projutil.InteractiveHardOff
				}
			}

			c.setDefaults()

			if err := c.validate(); err != nil {
				return fmt.Errorf("invalid command options: %v", err)
			}
			if err := c.run(); err != nil {
				log.Fatalf("Error generating package manifests: %v", err)
			}

			return nil
		},
	}

	cmd.Flags().StringVar(&c.apisDir, "apis-dir", "", "Root directory for API type defintions")
	cmd.Flags().BoolVar(&c.interactive, "interactive", false, "When set or no package base exists, an interactive "+
		"command prompt will be presented to accept package ClusterServiceVersion metadata")

	c.addCommonFlagsTo(cmd.Flags())

	return cmd
}

func (c *packagemanifestsCmd) addCommonFlagsTo(fs *pflag.FlagSet) {
	fs.StringVar(&c.operatorName, "operator-name", "", "Name of the packaged operator")
	fs.StringVarP(&c.version, "version", "v", "", "Semantic version of the packaged operator")
	fs.StringVar(&c.fromVersion, "from-version", "", "Semantic version of the operator being upgraded from")
	fs.StringVar(&c.inputDir, "input-dir", "", "Directory to read existing package manifests from. "+
		"This directory is the parent of individual versioned package directories, and different from --deploy-dir")
	fs.StringVar(&c.outputDir, "output-dir", "", "Directory in which to write package manifests")
	fs.StringVar(&c.deployDir, "deploy-dir", "", "Root directory for operator manifests such as "+
		"Deployments and RBAC, ex. 'deploy'. This directory is different from that passed to --input-dir")
	fs.StringVar(&c.crdsDir, "crds-dir", "", "Root directory for CustomResoureDefinition manifests")
	fs.StringVar(&c.channelName, "channel", "", "Channel name for the generated package")
	fs.BoolVar(&c.isDefaultChannel, "default-channel", false, "Use the channel passed to --channel "+
		"as the package manifest file's default channel")
	fs.BoolVar(&c.updateCRDs, "update-crds", true, "Update CustomResoureDefinition manifests in this package")
	fs.BoolVarP(&c.quiet, "quiet", "q", false, "Run in quiet mode")
}
