#!/usr/bin/env python3
"""Generate a fresh Ed25519 keypair for production license signing.

**Creator-only.** Run once, write the private key somewhere safe,
configure the build pipeline with the public key.

Usage::

    python scripts/generate_keypair.py
    python scripts/generate_keypair.py --json
    python scripts/generate_keypair.py --output keys.txt

The output looks like::

    DATATOOLS_LICENSE_PRIVKEY=<64 hex chars>     # KEEP SECRET
    DATATOOLS_LICENSE_PUBKEY=<64 hex chars>      # BAKE INTO BUILD

The private key never goes near the buyer-facing binary. Stash it in
a password manager / KMS / hardware token; the only places it gets
loaded are:

- ``scripts/generate_license.py`` when minting a buyer's blob
- Your CI's signing step, if you've automated blob minting

The public key gets set as ``DATATOOLS_LICENSE_PUBKEY`` in the
PyInstaller build env (so the shipped binary verifies against it),
and the production-safe runtime check refuses to start any frozen
build that's still using the in-source dev key.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey


def generate() -> tuple[str, str]:
    """Return ``(private_hex, public_hex)`` for a fresh keypair."""
    priv = Ed25519PrivateKey.generate()
    priv_hex = priv.private_bytes(
        encoding=serialization.Encoding.Raw,
        format=serialization.PrivateFormat.Raw,
        encryption_algorithm=serialization.NoEncryption(),
    ).hex()
    pub_hex = priv.public_key().public_bytes(
        encoding=serialization.Encoding.Raw,
        format=serialization.PublicFormat.Raw,
    ).hex()
    return priv_hex, pub_hex


def main(argv: list[str] | None = None) -> int:
    p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    p.add_argument("--json", action="store_true", help="Emit JSON instead of env-file format.")
    p.add_argument("--output", "-o", type=Path, default=None, help="Write to this file instead of stdout.")
    args = p.parse_args(argv)

    priv_hex, pub_hex = generate()

    if args.json:
        payload = json.dumps(
            {"private_key": priv_hex, "public_key": pub_hex},
            indent=2,
        )
    else:
        payload = (
            f"# DataTools license keypair — generated by generate_keypair.py\n"
            f"# KEEP THE PRIVATE KEY SECRET. Lose it and your existing\n"
            f"# licenses can't be renewed (you'd have to ship a new build\n"
            f"# with a new public key and re-issue every active license).\n"
            f"\n"
            f"DATATOOLS_LICENSE_PRIVKEY={priv_hex}\n"
            f"DATATOOLS_LICENSE_PUBKEY={pub_hex}\n"
        )

    if args.output:
        args.output.write_text(payload + "\n", encoding="utf-8")
        # chmod 600 — best-effort; ignored on Windows.
        try:
            args.output.chmod(0o600)
        except OSError:
            pass
        print(f"Wrote {args.output} (mode 600)", file=sys.stderr)
    else:
        print(payload)

    print(
        "\nNext steps:\n"
        "  1. Store the private key in your password manager.\n"
        "  2. Bake the public key into the PyInstaller build:\n"
        "       DATATOOLS_LICENSE_PUBKEY=<pubkey> pyinstaller ...\n"
        "  3. Mint buyer licenses by setting the private key:\n"
        "       DATATOOLS_LICENSE_PRIVKEY=<privkey> "
        "python scripts/generate_license.py --name 'Buyer' --email b@x.com\n",
        file=sys.stderr,
    )
    return 0


if __name__ == "__main__":
    sys.exit(main())
