#!/usr/bin/env python3
"""
StarGateWB — esempio runnable: ALLEGATI cifrati (inline + chunked) in Python.

Gli allegati si cifrano con la STESSA chiave AES del compartimento `content` della
segnalazione (usata in submit e incartata nei key_wrappers verso K_content). Il server
riceve SOLO ciphertext. Parità con attachments.js. Dipendenza: `pip install cryptography`.

Uso:
  HOST=https://api.vocea.cloud API_KEY=wbk_... SLUG=acme-clientespa \
  CODE=WB-XXXX CONTENT_KEY_B64=<chiave AES content base64 32B> \
  python3 attachments.py ./prova.pdf
"""
import base64, json, math, os, sys, urllib.request
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

HOST = os.environ.get("HOST", "https://api.vocea.cloud").rstrip("/")
API = f"{HOST}/v1/integrations"
API_KEY = os.environ.get("API_KEY"); SLUG = os.environ.get("SLUG"); CODE = os.environ.get("CODE")
CONTENT_KEY = base64.b64decode(os.environ.get("CONTENT_KEY_B64", ""))
FILE = sys.argv[1] if len(sys.argv) > 1 else None
if not (API_KEY and SLUG and CODE and len(CONTENT_KEY) == 32 and FILE):
    sys.exit("Servono: API_KEY, SLUG, CODE, CONTENT_KEY_B64 (32B), e il path del file.")

INLINE_MAX = 50 * 1024 * 1024
CHUNK = 8 * 1024 * 1024
b64 = lambda b: base64.b64encode(b).decode()
_aes = AESGCM(CONTENT_KEY)

def gcm(plaintext: bytes):
    iv = os.urandom(12)
    ct_tag = _aes.encrypt(iv, plaintext, None)   # ct||tag (tag = ultimi 16 byte)
    return iv, ct_tag[:-16], ct_tag[-16:]

def http(path, method="GET", body_json=None, raw=None):
    headers = {"X-API-Key": API_KEY}
    data = None
    if body_json is not None:
        headers["Content-Type"] = "application/json"; data = json.dumps(body_json).encode()
    elif raw is not None:
        headers["Content-Type"] = "application/octet-stream"; data = raw
    req = urllib.request.Request(API + path, data=data, method=method, headers=headers)
    with urllib.request.urlopen(req) as r:
        txt = r.read().decode()
        return json.loads(txt) if txt else {}

def upload_inline(buf, name, mime):
    c_iv, c_ct, c_tag = gcm(buf)
    f_iv, f_ct, f_tag = gcm(name.encode())
    m_iv, m_ct, m_tag = gcm(mime.encode())
    out = http(f"/reports/{CODE}/attachments", "POST", body_json={
        "content_ciphertext": b64(c_ct), "content_iv": b64(c_iv), "content_auth_tag": b64(c_tag),
        "filename_iv": b64(f_iv), "original_filename_encrypted": b64(f_ct + f_tag),  # filename: ct||tag
        "mime_type_encrypted": b64(m_iv + m_ct + m_tag),                             # mime inline: iv||ct||tag
    })
    print("inline OK:", json.dumps(out))

def upload_chunked(buf, name, mime):
    f_iv, f_ct, f_tag = gcm(name.encode())
    m_iv, m_ct, m_tag = gcm(mime.encode())
    total = max(1, math.ceil(len(buf) / CHUNK))
    init = http(f"/reports/{CODE}/attachments/uploads", "POST", body_json={
        "original_filename_encrypted": b64(f_ct + f_tag), "filename_iv": b64(f_iv),  # chunked: ct||tag + iv proprio
        "mime_type_encrypted": b64(m_ct + m_tag), "mime_iv": b64(m_iv),
        "total_chunks": total, "total_size_bytes": len(buf),
    })
    for i in range(total):
        sl = buf[i * CHUNK:(i + 1) * CHUNK]
        iv, ct, tag = gcm(sl)                       # ogni chunk: cifratura indipendente
        http(f"/reports/{CODE}/attachments/uploads/{init['upload_id']}/chunks/{i}", "PUT", raw=iv + ct + tag)
        print(f"  chunk {i+1}/{total}", end="\r")
    done = http(f"/reports/{CODE}/attachments/uploads/{init['upload_id']}/complete", "POST")
    print("\nchunked OK:", json.dumps(done))

def main():
    with open(FILE, "rb") as fh:
        buf = fh.read()
    name = os.path.basename(FILE)
    mime = "application/pdf" if name.endswith(".pdf") else "video/mp4" if name.endswith(".mp4") else "application/octet-stream"
    print(f"File {name} · {len(buf)/1024/1024:.1f} MB → {'inline' if len(buf) <= INLINE_MAX else 'chunked'}")
    (upload_inline if len(buf) <= INLINE_MAX else upload_chunked)(buf, name, mime)

if __name__ == "__main__":
    main()
