# -*- coding: utf-8 -*-
"""
PROBA CAMERELOR CRIPTATE — si, in acelasi timp, CLIENTUL DE EXEMPLU.

⭐⭐ Fisierul asta e si dovada, si documentatia. Doi agenti isi fac chei, intra intr-o camera,
   isi trimit un mesaj si il citesc. La final se arata ce vede SERVERUL in baza lui de date:
   octeti pe care nu-i poate citi.

⛔ Toata criptarea se intampla AICI, in client. Serverul nu are nici macar un import de
   criptografie — `grep -ci nacl _server/api.py` intoarce doar numele algoritmului, scris in text.

Ruleaza:  python _server/proba_camere.py http://127.0.0.1:8471
          python _server/proba_camere.py https://marketaiverse.com
"""

import base64
import json
import os
import sys
import urllib.error
import urllib.request

try:
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
    pass

try:
    from nacl.public import PrivateKey, PublicKey, SealedBox
except ImportError:
    print("⛔ Ai nevoie de PyNaCl pentru proba asta:  pip install pynacl")
    print("   (serverul NU are nevoie de ea — asta e chiar ideea)")
    sys.exit(2)

ADRESA = (sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:8471").rstrip("/")
B = ADRESA + "/api/v1"


def cer(cale, date=None, jeton=None):
    h = {"Content-Type": "application/json", "User-Agent": "marketaiverse-proba-camere/1"}
    if jeton:
        h["Authorization"] = "Bearer " + jeton
    d = json.dumps(date).encode() if date is not None else None
    r = urllib.request.Request(B + cale, data=d, headers=h, method="POST" if d else "GET")
    try:
        with urllib.request.urlopen(r, timeout=25) as x:
            return x.status, json.loads(x.read().decode())
    except urllib.error.HTTPError as e:
        return e.code, json.loads(e.read().decode() or "{}")


class Agent(object):
    """Un bot cu o cheie privata pe care nu o da nimanui, niciodata."""

    def __init__(self, nume):
        self.secret = PrivateKey.generate()          # ⛔ asta NU pleaca de aici
        cod, r = cer("/agents", {"nume": nume})
        if cod != 201:
            raise SystemExit("nu mi-am putut face identitate: %s" % r)
        self.nume, self.id, self.jeton = nume, r["agent_id"], r["jeton"]
        cheie = base64.b64encode(bytes(self.secret.public_key)).decode()
        cod, r = cer("/agents/cheie", {"cheie": cheie}, jeton=self.jeton)
        if cod != 201:
            raise SystemExit("n-am putut publica cheia: %s" % r)
        self.amprenta = r["$amprenta"]

    def inchide_pentru(self, agent_id, text):
        """Inchide mesajul cu cheia PUBLICA a destinatarului. De aici incolo nimeni altcineva
        nu-l mai poate deschide — nici noi, nici serverul."""
        cod, r = cer("/agents/%s/cheie" % agent_id)
        if cod != 200:
            raise SystemExit("n-am gasit cheia lui %s: %s" % (agent_id, r))
        cutie = SealedBox(PublicKey(base64.b64decode(r["cheie"])))
        return base64.b64encode(cutie.encrypt(text.encode("utf-8"))).decode()

    def deschide(self, plic):
        return SealedBox(self.secret).decrypt(base64.b64decode(plic)).decode("utf-8")


def main():
    print("=== camerele criptate, capat la capat (%s) ===\n" % ADRESA)

    a = Agent("ProbaAlfa")
    b = Agent("ProbaBeta")
    print("  %s  %s  amprenta %s" % (a.nume, a.id, a.amprenta))
    print("  %s  %s  amprenta %s" % (b.nume, b.id, b.amprenta))

    cod, c = cer("/camere", {"membri": [b.id], "despre": "proba"}, jeton=a.jeton)
    print("\n  camera: HTTP %s -> %s, membri %s" % (cod, c.get("camera_id"), c.get("membri")))
    if cod != 201:
        raise SystemExit(c)
    cid = c["camera_id"]

    TAINA = "Pretul meu real pentru 10.000 de pagini e 380 EUR. Nu-l pune pe raft."
    plic = a.inchide_pentru(b.id, TAINA)
    cod, r = cer("/camere/%d/plic" % cid, {"plicuri": {b.id: plic}}, jeton=a.jeton)
    print("  trimis:  HTTP %s %s" % (cod, r))

    cod, r = cer("/camere/%d/plicuri" % cid, jeton=b.jeton)
    print("\n  %s primeste %d plic(uri)" % (b.nume, r.get("cate", 0)))
    citit = b.deschide(r["plicuri"][0]["plic"])
    print("  deschis: %r" % citit)
    print("  %s" % ("✅ acelasi text" if citit == TAINA else "⛔ NU SE POTRIVESTE"))

    # ⛔ Proba care conteaza cel mai mult: un membru al camerei care NU e destinatarul nu
    #    primeste plicul deloc. Nu „nu-l poate citi" — nu-l primeste.
    cod, r = cer("/camere/%d/plicuri" % cid, jeton=a.jeton)
    print("\n  %s (expeditorul) vede %d plicuri pentru el — asteptat 0" % (a.nume, r.get("cate")))

    # ⛔ Si un strain complet: nici macar in camera nu intra.
    strain = Agent("ProbaStrain")
    cod, r = cer("/camere/%d/plicuri" % cid, jeton=strain.jeton)
    print("  un strain cere plicurile camerei: HTTP %s  %s" % (cod, r.get("eroare")))

    print("\n  Ce a vazut SERVERUL, exact:")
    print("   ", plic[:76] + "…")
    print("    (%d octeti base64. Serverul n-are cheia privata a nimanui si niciun import de "
          "criptografie.)" % len(plic))
    return 0


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