#!/usr/bin/env python3
"""Шифрующий издатель (конверт E2E v1): эфемерный ECDH secp256r1 → HKDF-SHA256 → AES-256-GCM.

    notify.py <адрес-с-ключом> <заголовок> [текст] [ссылка]

Шифрование — ПО УМОЛЧАНИЮ. Ключ берётся:
1) из фрагмента адреса `…/t/<тема>#<pubkey>` (кнопка «Скопировать адрес» / QR), либо
2) автоматически с реле (`GET /t/<тема>/key`) — приложение само публикует свой ключ,
   а отпечаток проверяется здесь: SHA-256(ключ) обязан совпасть с темой, подмена
   ключа реле исключена по построению.
Открытым текстом — только если ключа нет нигде (тогда печатается предупреждение).
Требует пакет `cryptography`.
"""
import base64
import json
import os
import sys
import urllib.request


def b64u(b: bytes) -> str:
    return base64.urlsafe_b64encode(b).decode().rstrip("=")


def b64d(s: str) -> bytes:
    return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))


def main() -> None:
    if len(sys.argv) < 3:
        sys.exit("использование: notify.py <адрес[#ключ]> <заголовок> [текст] [ссылка]")
    addr, title = sys.argv[1], sys.argv[2]
    payload = {"title": title, "body": sys.argv[3] if len(sys.argv) > 3 else ""}
    if len(sys.argv) > 4:
        payload["url"] = sys.argv[4]

    import hashlib
    url, _, pub_b64 = addr.partition("#")
    topic = url.rstrip("/").rsplit("/", 1)[-1]

    if not pub_b64:  # ключа в адресе нет — спросим реле и проверим отпечаток
        try:
            with urllib.request.urlopen(url + "/key", timeout=10) as r:
                j = json.load(r)
            fp = b64u(hashlib.sha256(b64d(j["pub"])).digest())
            # сегмент URL может быть издательским write-адресом — читающую тему
            # (она же отпечаток) реле отдаёт в ответе; сверка остаётся обязательной
            if fp == j.get("topic", topic):
                pub_b64 = j["pub"]
            else:
                print("ВНИМАНИЕ: ключ с реле не совпал с темой — игнорирую", file=sys.stderr)
        except Exception:
            pass

    if pub_b64:
        # крипто-контекст конверта — всегда читающая тема = отпечаток ключа
        topic = b64u(hashlib.sha256(b64d(pub_b64)).digest())

    if pub_b64:
        from cryptography.hazmat.primitives import hashes, serialization
        from cryptography.hazmat.primitives.asymmetric import ec
        from cryptography.hazmat.primitives.ciphers.aead import AESGCM
        from cryptography.hazmat.primitives.kdf.hkdf import HKDF

        pub = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), b64d(pub_b64))
        eph = ec.generate_private_key(ec.SECP256R1())
        shared = eph.exchange(ec.ECDH(), pub)
        iv = os.urandom(12)
        key = HKDF(algorithm=hashes.SHA256(), length=32, salt=iv,
                   info=b"AVN1" + topic.encode()).derive(shared)
        ct = AESGCM(key).encrypt(iv, json.dumps(payload, ensure_ascii=False).encode(), None)
        epk = eph.public_key().public_bytes(
            serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint)
        body = {"v": 1, "epk": b64u(epk), "iv": b64u(iv), "ct": b64u(ct)}
    else:
        body = payload
        print("(ключ темы не найден — отправляю открытым текстом)", file=sys.stderr)

    req = urllib.request.Request(
        url, json.dumps(body, ensure_ascii=False).encode(),
        {"Content-Type": "application/json"})
    print(urllib.request.urlopen(req).read().decode())


if __name__ == "__main__":
    main()
