// Copyright 2024 The Sigstore 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 main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/sigstore/sigstore-go/pkg/bundle"
)

var bundlePath *string
var certOIDC *string
var certSAN *string
var identityToken *string
var trustedRootPath *string
var signingConfigPath *string
var keyPath *string
var inToto bool

func usage() {
	fmt.Println("Usage:")
	fmt.Printf("\t%s sign-bundle [--in-toto] --identity-token TOKEN [--signing-config FILE] [--trusted-root FILE] --bundle FILE FILE\n", os.Args[0])
	fmt.Printf("\t%s verify-bundle --bundle FILE [--certificate-identity IDENTITY --certificate-oidc-issuer URL] [--key FILE] [--trusted-root FILE] FILE\n", os.Args[0])
}

func parseArgs() {
	for i := 2; i < len(os.Args); {
		switch os.Args[i] {
		// TODO: support staging (see https://github.com/sigstore/cosign/issues/2434)
		//
		// Today cosign signing does not yet use sigstore-go, and so we would
		// need to make some clever invocation of `cosign initialize` to
		// support staging. Instead it might make sense to wait for cosign
		// signing to use sigstore-go.
		case "--bundle":
			bundlePath = &os.Args[i+1]
			i += 2
		case "--certificate-oidc-issuer":
			certOIDC = &os.Args[i+1]
			i += 2
		case "--certificate-identity":
			certSAN = &os.Args[i+1]
			i += 2
		case "--identity-token":
			identityToken = &os.Args[i+1]
			i += 2
		case "--trusted-root":
			trustedRootPath = &os.Args[i+1]
			i += 2
		case "--signing-config":
			signingConfigPath = &os.Args[i+1]
			i += 2
		case "--key":
			keyPath = &os.Args[i+1]
			i += 2
		case "--in-toto":
			inToto = true
			i++
		default:
			i++
		}
	}
}

func main() {
	if len(os.Args) < 2 {
		usage()
		os.Exit(1)
	}

	parseArgs()

	args := []string{}

	switch os.Args[1] {
	case "sign-bundle":
		if inToto {
			args = append(args, "attest-blob")
		} else {
			args = append(args, "sign-blob")
		}
		args = append(args, "-y")

	case "verify-bundle":
		args = append(args, "verify-blob")

		// How do we know if we should expect signed timestamps or not?
		// Let's crack open the bundle
		if bundlePath != nil {
			b, err := bundle.LoadJSONFromPath(*bundlePath)
			if err != nil {
				log.Fatal(err)
			}
			ts, err := b.Timestamps()
			if err != nil {
				log.Fatal(err)
			}
			if len(ts) > 0 {
				args = append(args, "--use-signed-timestamps")
			}
		}

	default:
		log.Fatalf("Unsupported command %q", os.Args[1]) // #nosec G706 -- CLI tool, args are operator-supplied
	}

	if bundlePath != nil {
		args = append(args, "--bundle", *bundlePath)
		args = append(args, "--new-bundle-format")
	}
	if identityToken != nil {
		args = append(args, "--identity-token", *identityToken)
	}
	if certSAN != nil {
		args = append(args, "--certificate-identity", *certSAN)
	}
	if certOIDC != nil {
		args = append(args, "--certificate-oidc-issuer", *certOIDC)
	}
	if trustedRootPath != nil {
		args = append(args, "--trusted-root", *trustedRootPath)
	}
	if signingConfigPath != nil {
		args = append(args, "--signing-config", *signingConfigPath)
	}
	if keyPath != nil {
		args = append(args, "--key", *keyPath)
	}
	if inToto {
		args = append(args, "--statement")
	}
	args = append(args, os.Args[len(os.Args)-1])

	dir := filepath.Dir(os.Args[0])
	initCmd := exec.Command(filepath.Join(dir, "cosign"), "initialize") // #nosec G204,G702 -- conformance harness invokes the sibling cosign binary
	err := initCmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	cmd := exec.Command(filepath.Join(dir, "cosign"), args...) // #nosec G204,G702 -- conformance harness invokes the sibling cosign binary
	var out strings.Builder
	cmd.Stdout = &out
	cmd.Stderr = &out
	err = cmd.Run()

	fmt.Println(out.String())

	if err != nil {
		log.Fatal(err)
	}
}
