// Command bj-license — инструмент издателя: генерация ключей подписи,
// выпуск годовых лицензий и проверка.
//
//	bj-license keygen -out ./keys/license
//	bj-license issue -tenant "ООО Ромашка" -plan pro -days 365 \
//	    -features updates,web-cabinet -key ./keys/license.priv -keyid main
//	bj-license verify -key-file license.key -pub ./keys/license.pub
package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"os"
	"strings"
	"time"

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

// newUUID — UUID v4 без внешних зависимостей.
func newUUID() string {
	var b [16]byte
	_, _ = rand.Read(b[:])
	b[6] = (b[6] & 0x0f) | 0x40 // версия 4
	b[8] = (b[8] & 0x3f) | 0x80 // вариант
	h := hex.EncodeToString(b[:])
	return h[0:8] + "-" + h[8:12] + "-" + h[12:16] + "-" + h[16:20] + "-" + h[20:32]
}

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

func usage() {
	fmt.Fprintln(os.Stderr, "bj-license keygen -out <prefix>")
	fmt.Fprintln(os.Stderr, "bj-license issue -tenant <name> -plan free|pro|enterprise -days <n> -features a,b -key <priv> [-keyid id] [-max-nodes n] [-note txt]")
	fmt.Fprintln(os.Stderr, "bj-license verify -key-file <license.key> -pub <pubkey.pub>")
	os.Exit(2)
}

func keygen(args []string) {
	out := "license"
	for i := 0; i < len(args)-1; i++ {
		if args[i] == "-out" {
			out = args[i+1]
		}
	}
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		fatal("keygen: %v", err)
	}
	if err := os.WriteFile(out+".priv", []byte(base64.StdEncoding.EncodeToString(priv.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("Публичный ключ (зашить в bj-server):\n  %s\n", base64.StdEncoding.EncodeToString(pub))
}

func issue(args []string) {
	a := parseArgs(args)
	tenant := a["tenant"]
	keyPath := a["key"]
	if tenant == "" || keyPath == "" {
		fatal("issue: требуются -tenant и -key")
	}
	plan := license.Plan(orDefault(a["plan"], "pro"))
	days := atoiDefault(a["days"], 365)
	keyID := orDefault(a["keyid"], "main")

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

	now := time.Now().UTC()
	var feats []string
	if a["features"] != "" {
		feats = strings.Split(a["features"], ",")
	}
	l := &license.License{
		Schema:    license.CurrentSchema,
		ID:        newUUID(),
		Tenant:    tenant,
		Product:   "bj-server",
		Plan:      plan,
		IssuedAt:  now,
		ExpiresAt: now.AddDate(0, 0, days),
		Features:  feats,
		MaxNodes:  atoiDefault(a["max-nodes"], 0),
		Note:      a["note"],
	}
	tok, err := license.Sign(l, priv, keyID)
	if err != nil {
		fatal("sign: %v", err)
	}
	fmt.Printf("Лицензия выпущена: tenant=%q plan=%s до %s (%d дней)\n",
		tenant, plan, l.ExpiresAt.Format("02.01.2006"), days)
	fmt.Printf("ID: %s\n", l.ID)
	fmt.Println("Ключ для клиента (вставить в bj-server → Лицензия):")
	fmt.Println(tok.Encode())
}

func verify(args []string) {
	a := parseArgs(args)
	if a["key-file"] == "" || a["pub"] == "" {
		fatal("verify: требуются -key-file и -pub")
	}
	raw, err := os.ReadFile(a["key-file"])
	if err != nil {
		fatal("read key-file: %v", err)
	}
	pubB, err := os.ReadFile(a["pub"])
	if err != nil {
		fatal("read pub: %v", err)
	}
	pub, err := license.ParsePublicKey(strings.TrimSpace(string(pubB)))
	if err != nil {
		fatal("pub: %v", err)
	}
	tok, err := license.DecodeToken(string(raw))
	if err != nil {
		fatal("decode: %v", err)
	}
	l, err := license.Verify(tok, pub)
	if err != nil {
		fatal("verify: %v", err)
	}
	now := time.Now().UTC()
	fmt.Printf("Подпись валидна. tenant=%q plan=%s\n", l.Tenant, l.Plan)
	fmt.Printf("Действует: %s — %s (осталось %d дней)\n",
		l.IssuedAt.Format("02.01.2006"), l.ExpiresAt.Format("02.01.2006"), l.DaysLeft(now))
	if err := l.Valid(now); err != nil {
		fmt.Printf("СТАТУС: %v\n", err)
	} else {
		fmt.Printf("СТАТУС: активна, обновления %v\n", l.AllowsUpdates())
	}
}

// --- helpers ---

func parseArgs(args []string) map[string]string {
	m := map[string]string{}
	for i := 0; i < len(args); i++ {
		if strings.HasPrefix(args[i], "-") && i+1 < len(args) {
			m[strings.TrimPrefix(args[i], "-")] = args[i+1]
			i++
		}
	}
	return m
}

func orDefault(s, def string) string {
	if s == "" {
		return def
	}
	return s
}

func atoiDefault(s string, def int) int {
	if s == "" {
		return def
	}
	var n int
	_, err := fmt.Sscanf(s, "%d", &n)
	if err != nil {
		return def
	}
	return n
}

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