#!/usr/bin/env python3
"""gen_secrets.py — выпуск НОВОГО комплекта секретов для нового хоста (ORCH-101).

Provisioning-инструмент тиража (ADR-001 D8): генерирует криптослучайные
webhook-секреты оркестратора и печатает .env-фрагмент с плейсхолдерами внешних
токенов. Боевые секреты текущего хоста НЕ копируются ни на одном шаге — для
нового хоста всегда выпускается новый комплект (AC-5).

Состав (инвентаризация FR-4.1):
  * генерируются локально (secrets.token_hex(32) — 32 байта энтропии, 64 hex):
      ORCH_PLANE_WEBHOOK_SECRET, ORCH_GITEA_WEBHOOK_SECRET
  * выпускаются внешними системами (только плейсхолдер + чек-лист в
    docs/operations/REPLICATION.md: где выпустить -> куда вписать -> как
    проверить): ORCH_PLANE_API_TOKEN, ORCH_PLANE_BOT_* (7, опциональны),
    ORCH_GITEA_TOKEN, ORCH_TELEGRAM_BOT_TOKEN (+ несекретный
    ORCH_TELEGRAM_CHAT_ID), sidecar WATCHDOG_TG_BOT_TOKEN / WATCHDOG_TG_CHAT_ID.

Поведение (NFR-3): по умолчанию — ТОЛЬКО печать в stdout (файлы не трогаются).
``--write [PATH]`` пишет фрагмент в файл (дефолт .env); СУЩЕСТВУЮЩИЙ файл ->
отказ с exit-кодом 2 и внятным сообщением; перезапись — только явный
``--force``. Повторный запуск всегда даёт другие значения секретов.

stdlib-only (secrets, argparse) — никаких зависимостей платформы; скрипт
работает на голом python3 целевого хоста до первого `docker compose up`.

Запуск:
    python3 scripts/gen_secrets.py                 # печать в stdout
    python3 scripts/gen_secrets.py --write         # создать .env (если его нет)
    python3 scripts/gen_secrets.py --write /tmp/e  # создать произвольный файл
    python3 scripts/gen_secrets.py --write --force # перезаписать существующий
"""

import argparse
import secrets
import sys

# Webhook-секреты, генерируемые локально на целевом хосте (FR-4.2).
GENERATED_KEYS = (
    "ORCH_PLANE_WEBHOOK_SECRET",
    "ORCH_GITEA_WEBHOOK_SECRET",
)

# Секреты внешних систем: только плейсхолдеры (значения выпускает оператор —
# чек-лист в docs/operations/REPLICATION.md). Имена согласованы с .env.example.
EXTERNAL_KEYS = (
    "ORCH_PLANE_API_TOKEN",
    "ORCH_PLANE_BOT_ANALYST",
    "ORCH_PLANE_BOT_ARCHITECT",
    "ORCH_PLANE_BOT_DEVELOPER",
    "ORCH_PLANE_BOT_REVIEWER",
    "ORCH_PLANE_BOT_TESTER",
    "ORCH_PLANE_BOT_DEPLOYER",
    "ORCH_PLANE_BOT_STREAM",
    "ORCH_GITEA_TOKEN",
    "ORCH_TELEGRAM_BOT_TOKEN",
    "ORCH_TELEGRAM_CHAT_ID",
    "WATCHDOG_TG_BOT_TOKEN",
    "WATCHDOG_TG_CHAT_ID",
)

# 32 байта энтропии -> 64 hex-символа (AC-5: >= 32 байт).
TOKEN_BYTES = 32


def generate_secret() -> str:
    """Криптослучайный webhook-секрет: 32 байта энтропии, hex-кодировка."""
    return secrets.token_hex(TOKEN_BYTES)


def build_fragment() -> str:
    """Собрать .env-фрагмент: свежие webhook-секреты + плейсхолдеры внешних.

    Каждый вызов генерирует НОВЫЕ значения (secrets — CSPRNG); детерминизма нет
    по построению (AC-5 «повторный запуск даёт другие значения»).
    """
    lines = [
        "# --- ORCH-101 gen_secrets.py: НОВЫЙ комплект секретов этого хоста ---",
        "# Сгенерировано локально; боевые секреты другого хоста сюда НЕ копируются.",
        "# Webhook-секреты вписать также в настройки webhook'ов Plane/Gitea",
        "# (см. docs/operations/REPLICATION.md и docs/operations/SETUP_WEBHOOKS.md).",
    ]
    for key in GENERATED_KEYS:
        lines.append(f"{key}={generate_secret()}")
    lines.append("# --- Внешние токены: выпустить по чек-листу REPLICATION.md ---")
    for key in EXTERNAL_KEYS:
        lines.append(f"{key}=")
    return "\n".join(lines) + "\n"


def main(argv: list[str] | None = None) -> int:
    """CLI. Возвращает exit-код (0 ok; 2 — отказ перезаписи без --force)."""
    parser = argparse.ArgumentParser(
        description="Выпуск нового комплекта секретов оркестратора (ORCH-101)."
    )
    parser.add_argument(
        "--write",
        nargs="?",
        const=".env",
        default=None,
        metavar="PATH",
        help="записать фрагмент в файл (дефолт .env); существующий файл -> отказ",
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="разрешить перезапись СУЩЕСТВУЮЩЕГО файла (явное подтверждение)",
    )
    args = parser.parse_args(argv)

    fragment = build_fragment()

    if args.write is None:
        # Режим по умолчанию: только печать, файловая система не трогается.
        sys.stdout.write(fragment)
        return 0

    path = args.write
    if not args.force:
        try:
            # x-mode: атомарный «создать только если не существует» — никогда
            # не перезаписывает существующий .env молча (NFR-3 / AC-5).
            with open(path, "x", encoding="utf-8") as f:
                f.write(fragment)
        except FileExistsError:
            sys.stderr.write(
                f"ОТКАЗ: {path} уже существует — молча не перезаписываю. "
                "Перезапись только с явным --force "
                "(или укажи другой путь: --write PATH).\n"
            )
            return 2
        except OSError as e:
            sys.stderr.write(f"ОШИБКА записи {path}: {e}\n")
            return 1
    else:
        try:
            with open(path, "w", encoding="utf-8") as f:
                f.write(fragment)
        except OSError as e:
            sys.stderr.write(f"ОШИБКА записи {path}: {e}\n")
            return 1

    sys.stderr.write(f"Записано: {path} (webhook-секреты сгенерированы заново)\n")
    return 0


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