package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"

	"github.com/pingcap-inc/nexuscrm-api-host/internal/gatewayrelease"
)

const (
	maxEvidenceBytes = int64(4 << 20)
	maxJWKSBytes     = int64(1 << 20)
)

func main() {
	if err := run(); err != nil {
		fmt.Fprintf(os.Stderr, "gateway release verifier: %v\n", err)
		os.Exit(1)
	}
}

func run() error {
	policyPath := flag.String("policy", "", "owner signing policy JSON")
	sourceCommit := flag.String("source-commit", "", "exact owner source commit")
	imageDigest := flag.String("image-digest", "", "exact image digest")
	evidencePath := flag.String("cosign-evidence", "", "Cosign verification JSON, or - for stdin")
	provenanceEvidencePath := flag.String("provenance-evidence", "", "Cosign-verified SLSA provenance DSSE JSON")
	jwksPath := flag.String("jwks", "", "optional pinned GitHub OIDC JWKS JSON")
	flag.Parse()

	if *policyPath == "" || *sourceCommit == "" || *imageDigest == "" || *evidencePath == "" || *provenanceEvidencePath == "" {
		return fmt.Errorf("policy, source-commit, image-digest, cosign-evidence, and provenance-evidence are required")
	}
	policyBytes, err := readBoundedFile(*policyPath, "owner policy", maxEvidenceBytes)
	if err != nil {
		return err
	}
	policy, err := gatewayrelease.LoadPolicy(bytes.NewReader(policyBytes))
	if err != nil {
		return err
	}
	jwks, err := loadJWKS(policy.OIDCJWKSURI, *jwksPath)
	if err != nil {
		return err
	}

	evidence, err := readEvidence(*evidencePath)
	if err != nil {
		return err
	}
	policyDigest := fmt.Sprintf("sha256:%x", sha256.Sum256(policyBytes))
	receipt, err := gatewayrelease.VerifyCosignEvidence(policy, *sourceCommit, *imageDigest, policyDigest, evidence, jwks, time.Now(), false)
	if err != nil {
		return err
	}
	provenanceEvidence, err := readEvidence(*provenanceEvidencePath)
	if err != nil {
		return err
	}
	provenanceDigest, err := gatewayrelease.VerifyProvenanceEvidence(
		policy,
		*sourceCommit,
		*imageDigest,
		receipt.RunID,
		receipt.RunAttempt,
		provenanceEvidence,
	)
	if err != nil {
		return err
	}
	receipt.ProvenanceSHA256 = provenanceDigest
	receipt.ProvenanceBuilderID = policy.ProvenanceBuilderID
	receipt.ProvenanceBuildType = policy.ProvenanceBuildType
	encoder := json.NewEncoder(os.Stdout)
	encoder.SetEscapeHTML(false)
	return encoder.Encode(receipt)
}

func loadJWKS(uri, path string) ([]byte, error) {
	if path != "" {
		return readBoundedFile(path, "pinned GitHub OIDC JWKS", maxJWKSBytes)
	}
	return gatewayrelease.FetchJWKS(&http.Client{
		Timeout: 10 * time.Second,
		CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
			return fmt.Errorf("GitHub OIDC JWKS redirect is not allowed")
		},
	}, uri)
}

func readEvidence(path string) ([]byte, error) {
	if path == "-" {
		return readBounded(os.Stdin, "Cosign verification evidence", maxEvidenceBytes)
	}
	return readBoundedFile(path, "Cosign verification evidence", maxEvidenceBytes)
}

func readBoundedFile(path, label string, limit int64) ([]byte, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("read %s: %w", label, err)
	}
	defer file.Close()
	return readBounded(file, label, limit)
}

func readBounded(reader io.Reader, label string, limit int64) ([]byte, error) {
	content, err := io.ReadAll(io.LimitReader(reader, limit+1))
	if err != nil {
		return nil, fmt.Errorf("read %s: %w", label, err)
	}
	if int64(len(content)) > limit {
		return nil, fmt.Errorf("%s exceeds size limit", label)
	}
	return content, nil
}
