package commands

import (
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/urfave/cli/v2"

	"github.com/aquasecurity/trivy-db/pkg/metadata"
	"github.com/aquasecurity/trivy-db/pkg/types"

	"github.com/pingcap-inc/security/baseline/pkg/commands/client"
	"github.com/pingcap-inc/security/baseline/pkg/commands/server"
	"github.com/pingcap-inc/security/baseline/pkg/utils"
)

// VersionInfo holds the trivy DB version Info
type VersionInfo struct {
	Version         string             `json:",omitempty"`
	VulnerabilityDB *metadata.Metadata `json:",omitempty"`
}

var (
	debugFlag = cli.BoolFlag{
		Name:    "debug",
		Aliases: []string{"d"},
		Usage:   "debug mode",
		EnvVars: []string{"BASELINE_DEBUG"},
	}

	quietFlag = cli.BoolFlag{
		Name:    "quiet",
		Aliases: []string{"q"},
		Usage:   "suppress log output",
		EnvVars: []string{"BASELINE_QUIET"},
	}

	cacheDirFlag = cli.StringFlag{
		Name:    "cache-dir",
		Value:   utils.DefaultCacheDir(),
		Usage:   "cache directory",
		EnvVars: []string{"BASELINE_CACHE_DIR"},
	}

	// Global flags
	globalFlags = []cli.Flag{
		&cacheDirFlag,
		&debugFlag,
		&quietFlag,
	}
)

func showVersion(cacheDir, version string, outputWriter io.Writer) {
	var dbMeta *metadata.Metadata

	mc := metadata.NewClient(cacheDir)
	meta, _ := mc.Get()
	if !meta.UpdatedAt.IsZero() && !meta.NextUpdate.IsZero() && meta.Version != 0 {
		dbMeta = &metadata.Metadata{
			Version:      meta.Version,
			NextUpdate:   meta.NextUpdate.UTC(),
			UpdatedAt:    meta.UpdatedAt.UTC(),
			DownloadedAt: meta.DownloadedAt.UTC(),
		}
	}

	output := fmt.Sprintf("Version: %s\n", version)
	if dbMeta != nil {
		output += fmt.Sprintf(`Vulnerability DB:
  Version: %d
  UpdatedAt: %s
  NextUpdate: %s
  DownloadedAt: %s
`, dbMeta.Version, dbMeta.UpdatedAt.UTC(), dbMeta.NextUpdate.UTC(), dbMeta.DownloadedAt.UTC())
	}
	fmt.Fprintf(outputWriter, output)
}

func NewApp(version string) *cli.App {
	cli.VersionPrinter = func(c *cli.Context) {
		showVersion(utils.TrivyCacheDir(c.String("cache-dir")), c.App.Version, c.App.Writer)
	}

	app := cli.NewApp()
	app.Name = "bl"
	app.HelpName = "bl"
	app.Version = version
	app.ArgsUsage = "target"
	app.Usage = "A simple and comprehensive baseline scanner for PingCAP"
	app.EnableBashCompletion = true
	app.Flags = globalFlags

	app.Commands = []*cli.Command{
		NewScannerCommand(),
		NewServerCommand(),
	}

	return app
}

// NewImageCommand is the factory method to add image subcommand
func NewImageCommand() *cli.Command {
	return &cli.Command{
		Name:      "image",
		Aliases:   []string{"i"},
		ArgsUsage: "target",
		Usage:     "image scanner",
		Action:    client.ScanImage,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "github-token",
				Value:   "github-token",
				Usage:   "expand rate limits",
				EnvVars: []string{"BASELINE_GITHUB_TOKEN"},
			},
			&cli.StringFlag{
				Name:    "severity",
				Value:   strings.Join(types.SeverityNames, ","),
				Usage:   "severities of vulnerabilities to be displayed (comma separated)",
				EnvVars: []string{"BASELINE_SEVERITY"},
			},
		},
	}
}

// NewNetworkCommand is the factory method to add network subcommand
func NewNetworkCommand() *cli.Command {
	return &cli.Command{
		Name:      "network",
		Aliases:   []string{"n"},
		ArgsUsage: "target",
		Usage:     "network scanner",
		Action:    client.ScanNetwork,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "target",
				Value:   "172.16.5.0/24",
				Usage:   "target network",
				EnvVars: []string{"BASELINE_TARGET_NETWORK"},
			},
		},
	}
}

// NewRepoCommand is the factory method to add repo subcommand
func NewRepoCommand() *cli.Command {
	return &cli.Command{
		Name:      "repo",
		Aliases:   []string{"r"},
		ArgsUsage: "target",
		Usage:     "repo scanner",
		Action:    client.ScanRepo,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "github-token",
				Value:   "github-token",
				Usage:   "use github token to expand rate limits",
				EnvVars: []string{"BASELINE_GITHUB_TOKEN"},
			},
			&cli.StringFlag{
				Name:    "severity",
				Value:   strings.Join(types.SeverityNames, ","),
				Usage:   "severities of vulnerabilities to be displayed (comma separated)",
				EnvVars: []string{"BASELINE_SEVERITY"},
			},
		},
	}
}

// NewScannerCommand is the factory method to add client command
func NewScannerCommand() *cli.Command {
	return &cli.Command{
		Name:    "client",
		Aliases: []string{"c"},
		Usage:   "scanner mode",
		Flags: []cli.Flag{
			&cli.DurationFlag{
				Name:    "timeout",
				Value:   time.Second * 300,
				Usage:   "timeout",
				EnvVars: []string{"BASELINE_TIMEOUT"},
			},
		},
		Subcommands: []*cli.Command{
			NewImageCommand(),
			NewNetworkCommand(),
			NewRepoCommand(),
		},
	}
}

// NewServerCommand is the factory method to add server command
func NewServerCommand() *cli.Command {
	return &cli.Command{
		Name:    "server",
		Aliases: []string{"s"},
		Usage:   "server mode",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "listen",
				Aliases: []string{"l"},
				Value:   "localhost:1337",
				Usage:   "listen address",
				EnvVars: []string{"BASELINE_SERVER_LISTEN"},
			},
			&cli.StringFlag{
				Name:    "github-token",
				Value:   "github-token",
				Usage:   "use github token to expand rate limits",
				EnvVars: []string{"BASELINE_GITHUB_TOKEN"},
			},
			&cli.DurationFlag{
				Name:    "timeout",
				Value:   time.Second * 300,
				Usage:   "timeout",
				EnvVars: []string{"BASELINE_TIMEOUT"},
			},
		},
		Action: server.Run,
	}
}
