// Command bj-release — инструмент издателя: генерация ключей подписи,
// сборка манифеста релиза из каталога артефактов и его подпись Ed25519.
//
// Использование:
//
//	bj-release keygen -out ./keys/signing
//	    → создаёт signing.priv (base64 seed) и signing.pub (base64 pubkey)
//
//	bj-release build -dir ./dist -version 1.2.0 -channel stable \
//	    -key ./keys/signing.priv -keyid main -out ./dist/manifest.json
//	    → хеширует все файлы в ./dist, собирает Manifest, подписывает,
//	      пишет SignedManifest в manifest.json
//
// Манифест подписывается целиком; клиент (bj-server auto-update) проверяет
// подпись зашитым публичным ключом ДО доверия версиям/хешам.
package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release"
)

func main() {
	if len(os.Args) < 2 {
		usage()
	}
	switch os.Args[1] {
	case "keygen":
		keygen(os.Args[2:])
	case "build":
		build(os.Args[2:])
	default:
		usage()
	}
}

func usage() {
	fmt.Fprintln(os.Stderr, "bj-release keygen -out <prefix>")
	fmt.Fprintln(os.Stderr, "bj-release build -dir <artifacts> -version <v> -channel <c> -key <priv> -keyid <id> -out <manifest.json> [-notes <txt>]")
	os.Exit(2)
}

func keygen(args []string) {
	fs := flag.NewFlagSet("keygen", flag.ExitOnError)
	out := fs.String("out", "signing", "префикс файлов ключей (создаст <out>.priv и <out>.pub)")
	_ = fs.Parse(args)

	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		fatal("keygen: %v", err)
	}
	seed := priv.Seed()
	if err := os.WriteFile(*out+".priv", []byte(base64.StdEncoding.EncodeToString(seed)+"\n"), 0o600); err != nil {
		fatal("write priv: %v", err)
	}
	if err := os.WriteFile(*out+".pub", []byte(base64.StdEncoding.EncodeToString(pub)+"\n"), 0o644); err != nil {
		fatal("write pub: %v", err)
	}
	fmt.Printf("Приватный ключ: %s.priv (НЕ КОММИТИТЬ, держать в секрете)\n", *out)
	fmt.Printf("Публичный ключ: %s.pub\n", *out)
	fmt.Printf("Публичный ключ (зашить в bj-server):\n  %s\n", base64.StdEncoding.EncodeToString(pub))
}

func build(args []string) {
	fs := flag.NewFlagSet("build", flag.ExitOnError)
	dir := fs.String("dir", "./dist", "каталог с артефактами")
	version := fs.String("version", "", "версия релиза, напр. 1.2.0")
	channel := fs.String("channel", "stable", "канал: stable|beta")
	keyPath := fs.String("key", "", "путь к приватному ключу (base64 seed)")
	keyID := fs.String("keyid", "main", "идентификатор ключа")
	out := fs.String("out", "", "путь для записи manifest.json (по умолчанию <dir>/manifest.json)")
	notes := fs.String("notes", "", "заметки к релизу")
	_ = fs.Parse(args)

	if *version == "" || *keyPath == "" {
		fatal("build: требуются -version и -key")
	}
	if *out == "" {
		*out = filepath.Join(*dir, "manifest.json")
	}

	priv, err := release.LoadPrivateKey(*keyPath)
	if err != nil {
		fatal("load key: %v", err)
	}

	// Имена артефактов, которые издаём (логическое имя → ставить +x).
	known := map[string]bool{
		"bj-server":            true,  // Go-бинарь
		"crypto-service.jar":   false, // Java сайдкар
		"install-validata.sh":  true,
		"install.sh":           true,
		"configure-ish.sql":    false,
	}

	entries, err := os.ReadDir(*dir)
	if err != nil {
		fatal("read dir: %v", err)
	}
	var arts []release.Artifact
	for _, e := range entries {
		if e.IsDir() || e.Name() == "manifest.json" {
			continue
		}
		full := filepath.Join(*dir, e.Name())
		sha, size, err := release.HashFile(full)
		if err != nil {
			fatal("hash %s: %v", e.Name(), err)
		}
		exec, ok := known[e.Name()]
		if !ok {
			// неизвестный файл — включаем, +x по расширению
			exec = strings.HasSuffix(e.Name(), ".sh")
		}
		arts = append(arts, release.Artifact{
			Name:    e.Name(),
			File:    e.Name(),
			Version: *version,
			SHA256:  sha,
			Size:    size,
			Exec:    exec,
		})
	}
	sort.Slice(arts, func(i, j int) bool { return arts[i].Name < arts[j].Name })
	if len(arts) == 0 {
		fatal("build: в каталоге %s нет артефактов", *dir)
	}

	m := &release.Manifest{
		Schema:     release.CurrentSchema,
		Version:    *version,
		Channel:    *channel,
		ReleasedAt: time.Now().UTC(),
		Notes:      *notes,
		Artifacts:  arts,
	}
	sm, err := release.Sign(m, priv, *keyID)
	if err != nil {
		fatal("sign: %v", err)
	}
	b, err := json.MarshalIndent(sm, "", "  ")
	if err != nil {
		fatal("marshal: %v", err)
	}
	if err := os.WriteFile(*out, b, 0o644); err != nil {
		fatal("write manifest: %v", err)
	}
	fmt.Printf("Манифест %s: версия %s, канал %s, артефактов %d, подписан ключом %s\n",
		*out, *version, *channel, len(arts), *keyID)
	for _, a := range arts {
		fmt.Printf("  %-22s %10d B  %s\n", a.Name, a.Size, a.SHA256[:16])
	}
}

func fatal(format string, a ...any) {
	fmt.Fprintf(os.Stderr, "bj-release: "+format+"\n", a...)
	os.Exit(1)
}
