// Copyright 2022 The OPA Authors.  All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.

package cmd

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/open-policy-agent/opa/cmd/internal/env"
	"github.com/open-policy-agent/opa/v1/ast"
	"github.com/spf13/cobra"
)

type capabilitiesParams struct {
	showCurrent  bool
	version      string
	file         string
	v0Compatible bool
}

func (p *capabilitiesParams) regoVersion() ast.RegoVersion {
	if p.v0Compatible {
		return ast.RegoV0
	}
	return ast.DefaultRegoVersion
}

func initCapabilities(root *cobra.Command, brand string) {
	executable := root.Name()

	capabilitiesParams := capabilitiesParams{}

	var capabilitiesCommand = &cobra.Command{
		Use:   "capabilities",
		Short: "Print the capabilities of " + brand,
		Long: `Show capabilities for ` + brand + `.

The 'capabilities' command prints the ` + brand + ` capabilities, prior to and including the version of ` + brand + ` used.

Print a list of all existing capabilities version names

    $ ` + executable + ` capabilities
    v0.17.0
    v0.17.1
    ...
    v0.37.1
    v0.37.2
    v0.38.0
    ...

Print the capabilities of the current version

    $ ` + executable + ` capabilities --current
    {
        "builtins": [...],
        "future_keywords": [...],
        "wasm_abi_versions": [...]
    }

Print the capabilities of a specific version

    $ ` + executable + ` capabilities --version v0.32.1
    {
        "builtins": [...],
        "future_keywords": null,
        "wasm_abi_versions": [...]
    }

Print the capabilities of a capabilities file

    $ ` + executable + ` capabilities --file ./capabilities/v0.32.1.json
    {
        "builtins": [...],
        "future_keywords": null,
        "wasm_abi_versions": [...]
    }

`,
		PreRunE: func(cmd *cobra.Command, _ []string) error {
			return env.CmdFlags.CheckEnvironmentVariables(cmd)
		},
		RunE: func(cmd *cobra.Command, _ []string) error {
			cmd.SilenceErrors = true
			cmd.SilenceUsage = true

			cs, err := doCapabilities(capabilitiesParams)
			if err != nil {
				return err
			}
			fmt.Println(cs)
			return nil
		},
	}
	capabilitiesCommand.Flags().BoolVar(&capabilitiesParams.showCurrent, "current", false, "print current capabilities")
	capabilitiesCommand.Flags().StringVar(&capabilitiesParams.version, "version", "", "print capabilities of a specific version")
	capabilitiesCommand.Flags().StringVar(&capabilitiesParams.file, "file", "", "print capabilities defined by a file")
	addV0CompatibleFlag(capabilitiesCommand.Flags(), &capabilitiesParams.v0Compatible)

	root.AddCommand(capabilitiesCommand)
}

func doCapabilities(params capabilitiesParams) (string, error) {
	var (
		c   *ast.Capabilities
		err error
	)

	if len(params.version) > 0 {
		c, err = ast.LoadCapabilitiesVersion(params.version)
	} else if len(params.file) > 0 {
		c, err = ast.LoadCapabilitiesFile(params.file)
	} else if params.showCurrent {
		c = ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(params.regoVersion()))
	} else {
		return showVersions()
	}

	if err != nil {
		return "", err
	}

	bs, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return "", err
	}
	return string(bs), nil

}

func showVersions() (string, error) {
	cvs, err := ast.LoadCapabilitiesVersions()
	if err != nil {
		return "", err
	}

	t := strings.Join(cvs, "\n")
	return t, nil
}
