// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// A stand-alone Go module that generates ../schema.go using the upstream
// x509-limbo JSON schema document.
//
// We maintain this in a separate Go module and vendor the resulting generated
// .go code to avoid the standard library taking a direct dependency on the
// C2SP/x509-limbo or atombender/go-jsonschema modules.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/atombender/go-jsonschema/pkg/generator"
)

// x509LimboVersion is the github.com/C2SP/x509-limbo module version used to
// generate ../schema.go. Update this to retarget a different upstream revision.
//
// x509-limbo publishes no Go packages, so we can't pin the version via a
// `require` directive (any `go mod tidy` would drop it) or a `tool` directive
// (no main package). Instead we fetch the module on demand via `go mod
// download` below, and propagate this constant to ../schemaversion.go so the
// runtime test fetches a matching revision.
const x509LimboVersion = "v0.0.0-20260522003327-feb7caccc1af"

const x509LimboModule = "github.com/C2SP/x509-limbo"

func main() {
	limboDir, err := downloadModuleDir(x509LimboModule, x509LimboVersion)
	if err != nil {
		log.Fatalf("failed to fetch %s@%s: %v", x509LimboModule, x509LimboVersion, err)
	}

	outputName := "schema.go"
	gen, err := generator.New(generator.Config{
		DefaultPackageName: "x509limbo",
		DefaultOutputName:  outputName,
		Tags:               []string{"json"},
		Warner: func(message string) {
			log.Printf("go-jsonschema: %s", message)
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	schemaFile := filepath.Join(limboDir, "limbo-schema.json")
	if err := gen.DoFile(schemaFile); err != nil {
		log.Fatalf("error processing limbo-schema.json: %v\n", err)
	}

	sources, err := gen.Sources()
	if err != nil {
		log.Fatalf("error generating sources: %v\n", err)
	}
	if sourceCount := len(sources); sourceCount != 1 {
		log.Fatalf("expected to generate 1 source file, got %d\n", sourceCount)
	}
	content, ok := sources[outputName]
	if !ok {
		log.Fatalf("missing generated %q output file source", outputName)
	}
	outFile := filepath.Join("../", outputName)
	if err := os.WriteFile(outFile, content, 0644); err != nil {
		log.Fatalf("error writing file %s: %v\n", outFile, err)
	}

	// Write a sibling file recording the x509-limbo module version that the
	// generated schema.go was produced against. The x509limbo package uses
	// this constant to fetch matching test vectors at runtime. We avoid reading
	// go.sum at test time because it might not be available (e.g. if the test
	// binary was copied to a remote machine).
	const versionFile = "../schemaversion.go"
	versionSrc := fmt.Sprintf(`// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Code generated by _schema/schema_gen.go, DO NOT EDIT.

package x509limbo

// X509LimboModule is the module path of the upstream x509-limbo project.
const X509LimboModule = %q

// X509LimboVersion is the X509LimboModule version that schema.go was
// generated against.
const X509LimboVersion = %q
`, x509LimboModule, x509LimboVersion)
	if err := os.WriteFile(versionFile, []byte(versionSrc), 0644); err != nil {
		log.Fatalf("writing %s: %v", versionFile, err)
	}
}

// downloadModuleDir runs `go mod download` to ensure module@version is present
// in the module cache and returns the directory containing its source tree.
func downloadModuleDir(module, version string) (string, error) {
	cmd := exec.Command("go", "mod", "download", "-json", module+"@"+version)
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	out, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("%w\n%s", err, stderr.String())
	}
	var info struct {
		Dir   string
		Error string
	}
	if err := json.Unmarshal(out, &info); err != nil {
		return "", fmt.Errorf("parsing go mod download output: %w", err)
	}
	if info.Error != "" {
		return "", fmt.Errorf("%s", info.Error)
	}
	return info.Dir, nil
}
