#!/usr/bin/env python3
"""Mint a signed license blob for a buyer (LOCAL, break-glass).

.. warning::

   This script mints **locally**, without going through the license
   server. Prefer :mod:`src.admin_cli` (``datatools-admin mint``)
   for routine work — it writes to the authoritative ``licenses``
   Postgres table and emits the same blob.

   Reach for this script only when the server is unreachable and a
   buyer needs a license *right now*. Mints from here land in the
   local issuance JSONL log; you'll need to reconcile them into the
   server's DB afterwards.

Creator-only tool. Signs with the Ed25519 private key from
``$DATATOOLS_LICENSE_PRIVKEY`` (production) or the in-tree dev key
(local development).

Every successful mint also appends a record to the issuance log at
``~/.datatools-creator/issued.jsonl`` (override with
``$DATATOOLS_ISSUANCE_LOG``). That log is the creator-side system of
record for "who has a license" — useful for re-delivery, support, and
as the seed for the future server-side ``licenses`` table.

Examples
--------

Mint a 1-year CORE license for Jane Doe::

    python scripts/generate_license.py \\
        --name "Jane Doe" --email jane@example.com --tier core

Mint a 2-year PRO license and write the blob to a file::

    python scripts/generate_license.py \\
        --name "Acme Corp" --email ops@acme.com --tier pro \\
        --years 2 --output acme.dtlic

Mint with the production key (CI / manual fulfillment)::

    DATATOOLS_LICENSE_PRIVKEY=<prod-private-hex> \\
        python scripts/generate_license.py --name ... --email ...

The output is a single base64-encoded token starting with ``DTLIC2:``
— paste this whole string into the buyer's delivery email or
deliver as an attached ``.dtlic`` file.
"""

from __future__ import annotations

import argparse
import json
import os
import sys
import uuid
from pathlib import Path

# Make ``src.license`` importable when run from the repo root.
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(_PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(_PROJECT_ROOT))

from src.license import Tier  # noqa: E402
from src.license.crypto import encode_blob, sign  # noqa: E402
from src.license.features import all_features_for_tier  # noqa: E402
from src.license.schema import (  # noqa: E402
    License,
    _utcnow_iso,
    default_expiry_iso,
)


def default_issuance_log() -> Path:
    """Path to the local issuance log (creator-side ledger).

    Resolution order:

    1. ``$DATATOOLS_ISSUANCE_LOG`` (absolute path; useful for tests
       and for pointing at a shared / encrypted volume).
    2. ``~/.datatools-creator/issued.jsonl`` — separate from the
       buyer-facing ``~/.datatools/`` dir so it never gets bundled
       into a shipped install.
    """
    override = os.environ.get("DATATOOLS_ISSUANCE_LOG")
    if override:
        return Path(override).expanduser().resolve()
    return Path.home() / ".datatools-creator" / "issued.jsonl"


def append_issuance_log(record: dict, *, path: Path | None = None) -> Path | None:
    """Best-effort append of *record* to the issuance log.

    Returns the resolved path on success, ``None`` on IO failure
    (with a warning printed to stderr). We intentionally do not raise:
    the blob has already been minted by the time this runs, and losing
    one ledger row is strictly better than aborting after a successful
    mint and leaving the creator unsure whether to re-mint.
    """
    p = path or default_issuance_log()
    try:
        p.parent.mkdir(parents=True, exist_ok=True)
        with p.open("a", encoding="utf-8") as f:
            f.write(json.dumps(record, sort_keys=True) + "\n")
        try:
            p.chmod(0o600)
        except OSError:
            pass
        return p
    except OSError as e:
        print(
            f"WARNING: could not write issuance log at {p}: {e}\n"
            "         The blob above is still valid — record the mint "
            "manually.",
            file=sys.stderr,
        )
        return None


def build_args() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        description="Mint a signed DataTools license blob.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    p.add_argument("--name", required=True, help="Buyer's full name.")
    p.add_argument("--email", required=True, help="Buyer's email.")
    p.add_argument(
        "--tier",
        default=Tier.CORE.value,
        choices=[t.value for t in Tier],
        help="License tier (default: %(default)s).",
    )
    p.add_argument(
        "--years",
        type=int,
        default=1,
        help="License lifetime in years (default: %(default)s).",
    )
    p.add_argument(
        "--key",
        default=None,
        help="Override the auto-generated license key (default: random).",
    )
    p.add_argument(
        "--output",
        "-o",
        type=Path,
        default=None,
        help="Write the blob to this file (default: print to stdout).",
    )
    p.add_argument(
        "--no-log",
        action="store_true",
        help=(
            "Skip writing to the issuance log. Use for one-off test "
            "mints; do NOT use for real buyer fulfillment."
        ),
    )
    return p


def main(argv: list[str] | None = None) -> int:
    args = build_args().parse_args(argv)
    tier = Tier(args.tier)
    rid = uuid.uuid4().hex
    key = args.key or f"DT1-{tier.value.upper()}-{rid[:8]}-{rid[8:16]}"

    lic = License(
        name=args.name,
        email=args.email,
        license_key=key,
        tier=tier,
        features=all_features_for_tier(tier),
        issued_at=_utcnow_iso(),
        expires_at=default_expiry_iso(years=args.years),
        signature="",
    )
    signature = sign(lic.to_canonical_dict())
    payload = lic.to_canonical_dict()
    payload["signature"] = signature
    blob = encode_blob(payload)

    if not args.no_log:
        log_path = append_issuance_log({
            "license_key": lic.license_key,
            "name": lic.name,
            "email": lic.email,
            "tier": lic.tier.value,
            "issued_at": lic.issued_at,
            "expires_at": lic.expires_at,
            "blob": blob,
        })
    else:
        log_path = None

    if args.output:
        args.output.write_text(blob + "\n", encoding="utf-8")
        print(f"Wrote license to {args.output}", file=sys.stderr)
    else:
        print(blob)
    print(
        f"  name:    {lic.name}\n"
        f"  email:   {lic.email}\n"
        f"  tier:    {lic.tier.value}\n"
        f"  key:     {lic.license_key}\n"
        f"  expires: {lic.expires_at}",
        file=sys.stderr,
    )
    if log_path:
        print(f"  logged:  {log_path}", file=sys.stderr)
    return 0


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