// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you 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 main

import (
	_ "embed"
	"os"
	"runtime"

	"github.com/apache/skywalking-cli/internal/commands/alarm"
	"github.com/apache/skywalking-cli/internal/commands/browser"
	"github.com/apache/skywalking-cli/internal/commands/completion"
	"github.com/apache/skywalking-cli/internal/commands/dashboard"
	"github.com/apache/skywalking-cli/internal/commands/dependency"
	"github.com/apache/skywalking-cli/internal/commands/endpoint"
	"github.com/apache/skywalking-cli/internal/commands/event"
	"github.com/apache/skywalking-cli/internal/commands/healthcheck"
	"github.com/apache/skywalking-cli/internal/commands/hierarchy"
	"github.com/apache/skywalking-cli/internal/commands/install"
	"github.com/apache/skywalking-cli/internal/commands/instance"
	"github.com/apache/skywalking-cli/internal/commands/interceptor"
	"github.com/apache/skywalking-cli/internal/commands/layer"
	"github.com/apache/skywalking-cli/internal/commands/logs"
	"github.com/apache/skywalking-cli/internal/commands/menu"
	"github.com/apache/skywalking-cli/internal/commands/metrics"
	"github.com/apache/skywalking-cli/internal/commands/process"
	"github.com/apache/skywalking-cli/internal/commands/profiling"
	"github.com/apache/skywalking-cli/internal/commands/records"
	"github.com/apache/skywalking-cli/internal/commands/service"
	"github.com/apache/skywalking-cli/internal/commands/trace"
	"github.com/apache/skywalking-cli/internal/commands/tracev2"
	intutil "github.com/apache/skywalking-cli/internal/util"
	"github.com/apache/skywalking-cli/pkg/logger"
	"github.com/apache/skywalking-cli/pkg/util"

	"github.com/sirupsen/logrus"
	"github.com/urfave/cli/v2"
	"github.com/urfave/cli/v2/altsrc"
)

var (
	log     *logrus.Logger
	version string // Will be initialized when building
)

func init() {
	log = logger.Log

	if runtime.GOOS != "windows" {
		cli.AppHelpTemplate = util.AppHelpTemplate
		cli.CommandHelpTemplate = util.CommandHelpTemplate
		cli.SubcommandHelpTemplate = util.SubcommandHelpTemplate
	}
}

func main() {
	app := cli.NewApp()
	app.Name = "swctl"
	app.Usage = "The CLI (Command Line Interface) for Apache SkyWalking."
	app.UsageText = `Commands in SkyWalking CLI are organized into two levels,
in the form of "swctl --option <level1> --option <level2> --option",
there are options in each level, which should follow right after
the corresponding command, take the following command as example:

	$ swctl --debug service list --start="2019-11-11" --end="2019-11-12"

where "--debug" is is an option of "swctl", and since the "swctl" is
a top-level command, "--debug" is also called global option, and "--start"
is an option of the third level command "list", there is no option for the
second level command "service".

Generally, the second level commands are entity related, there are entities
like "service", "service instance", "metrics" in SkyWalking, and we have
corresponding sub-command like "service"; the third level commands are
operations on the entities, such as "list" command will list all the
services, service instances, etc.`
	app.Version = version

	flags := flags()

	app.Commands = []*cli.Command{
		browser.Command,
		endpoint.Command,
		instance.Command,
		service.Command,
		metrics.Command,
		trace.Command,
		tracev2.Command,
		healthcheck.Command,
		dashboard.Command,
		install.Command,
		event.Command,
		logs.Command,
		completion.Command,
		dependency.Command,
		alarm.Command,
		layer.Command,
		process.Command,
		profiling.Command,
		records.Command,
		menu.Command,
		hierarchy.Command,
	}

	app.Before = interceptor.BeforeChain(
		setUpCommandLineContext,
		expandConfigFile,
		tryConfigFile(flags),
	)

	app.Flags = flags
	app.CommandNotFound = intutil.CommandNotFound

	// Enable auto-completion.
	app.EnableBashCompletion = true
	cli.BashCompletionFlag = &cli.BoolFlag{
		Name:   "auto_complete",
		Hidden: true,
	}
	if err := app.Run(os.Args); err != nil {
		log.Fatalln(err)
	}
}

func flags() []cli.Flag {
	return []cli.Flag{
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:  "config",
			Value: "~/.skywalking.yml",
			Usage: "file `path` of the default configurations.",
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:     "base-url",
			Required: false,
			Usage:    "base `url` of the OAP backend graphql service",
			Value:    "http://127.0.0.1:12800/graphql",
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:     "grpc-addr",
			Usage:    "backend gRPC service address `<host:port>`",
			Value:    "127.0.0.1:11800",
			Required: false,
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:     "username",
			Required: false,
			Usage:    "`username` of basic authorization",
			Value:    "",
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:     "password",
			Required: false,
			Usage:    "`password` of basic authorization",
			Value:    "",
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:     "authorization",
			Required: false,
			Usage: "`authorization` header, can be something like `Basic base64(username:password)` or `Bearer jwt-token`, " +
				"if `authorization` is set, `--username` and `--password` are ignored",
			Value: "",
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:     "insecure",
			Required: false,
			Usage:    "skip TLS certificate verification",
			Value:    "",
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:     "timezone",
			Required: false,
			Usage: "specifies the `timezone` where `--start` and `--end` are based, in the form of `+0800`. " +
				"If `--timezone` is given in the command line option, then it's used directly. " +
				"If the backend support the timezone API (since 6.5.0), CLI will try to get the timezone from backend, " +
				"and use it. " +
				"Otherwise, the CLI will use the current timezone in the current machine.",
		}),
		altsrc.NewBoolFlag(&cli.BoolFlag{
			Name:     "debug",
			Required: false,
			Usage:    "enable debug mode, will print more detailed information at runtime",
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:     "display",
			Required: false,
			Usage: "display `style` of the result, supported styles are: `json`, `yaml`, `table`, `graph`. " +
				"(Not all display styles are supported in all commands due to data formats incompatibilities " +
				"and the limits of Ascii Graph, like coloring in terminal, " +
				"so in that cases please use `json`  or `yaml` instead.)",
			Value: "",
		}),
	}
}

func expandConfigFile(c *cli.Context) error {
	return c.Set("config", util.ExpandFilePath(c.String("config")))
}

func tryConfigFile(flags []cli.Flag) cli.BeforeFunc {
	return func(c *cli.Context) error {
		configFile := c.String("config")
		if bytes, err := os.ReadFile(configFile); err == nil {
			log.Debug("Using configurations:\n", string(bytes))

			err = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("config"))(c)
			if err != nil {
				return err
			}
		} else if os.IsNotExist(err) {
			log.Debugf("open %s no such file, skip loading configuration file\n", c.String("config"))
		} else {
			return err
		}

		return nil
	}
}

func setUpCommandLineContext(c *cli.Context) error {
	if c.Bool("debug") {
		log.SetLevel(logrus.DebugLevel)
		log.Debugln("Debug mode is enabled")
	}

	return nil
}
