#!/usr/bin/env python3
"""Write-only HTTPS sender. Reads anonymous UTF-8 Markdown from stdin."""
import json
import base64
import re
import os
import sys
import time
import uuid
from urllib.parse import urlsplit
from urllib.request import Request, build_opener, HTTPRedirectHandler
from urllib.error import HTTPError, URLError

class NoRedirect(HTTPRedirectHandler):
    def redirect_request(self, *_args, **_kwargs):
        return None


def encode64(value):
    return base64.urlsafe_b64encode(value).rstrip(b'=').decode('ascii')


def encrypt_message(markdown, public_key, session_id, request_id):
    try:
        from cryptography.hazmat.primitives import serialization, hashes
        from cryptography.hazmat.primitives.asymmetric import rsa, padding
        from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    except ImportError:
        raise ValueError('Install requirements-sender.txt before sending.') from None
    if not re.fullmatch(r'[A-Za-z0-9_-]{392}', public_key or ''):
        raise ValueError('A valid public encryption key is required.')
    key = serialization.load_der_public_key(base64.urlsafe_b64decode(public_key + '=' * (-len(public_key) % 4)))
    if not isinstance(key, rsa.RSAPublicKey) or key.key_size != 2048:
        raise ValueError('Expected a 2048-bit RSA public key.')
    aes_key, iv = os.urandom(32), os.urandom(12)
    context = ('student-relay:e2ee:v1:' + session_id + ':' + request_id).encode('utf-8')
    wrapped = key.encrypt(aes_key, padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=context))
    ciphertext = AESGCM(aes_key).encrypt(iv, markdown.encode('utf-8'), context)
    return {'v': 1, 'iv': encode64(iv), 'wrapped_key': encode64(wrapped), 'ciphertext': encode64(ciphertext), 'request_id': request_id}


def send(markdown, endpoint, token, request_id=None, public_key=None):
    url = urlsplit(endpoint)
    session = re.fullmatch(r'/api/sessions/([A-Za-z0-9_-]{43})/messages', url.path)
    if url.scheme != 'https' or not url.hostname or url.username or url.password or url.query or url.fragment or not session:
        raise ValueError('Use the copied HTTPS submission endpoint without URL credentials, query or fragment.')
    if not token or len(markdown.encode('utf-8')) > 100000 or not markdown.strip():
        raise ValueError('Supply a sender token and 1–100,000 bytes of anonymous Markdown.')
    request_id = request_id or str(uuid.uuid4())
    if not re.fullmatch(r'[A-Za-z0-9_-]{8,128}', request_id):
        raise ValueError('Invalid request identifier.')
    encrypted = encrypt_message(markdown, public_key or os.environ.get('STUDENT_RELAY_PUBLIC_KEY', ''), session[1], request_id)
    # Encrypt once; retries reuse the exact packet, including its nonce and wrapped key.
    payload = json.dumps({'encrypted': encrypted, 'request_id': request_id}).encode('utf-8')
    opener = build_opener(NoRedirect())
    for attempt in range(3):
        request = Request(endpoint, payload, {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token}, method='POST')
        try:
            with opener.open(request, timeout=15) as response:
                result = json.loads(response.read(4096))
            if not isinstance(result, dict) or result.get('updated') is not True or type(result.get('version')) is not int:
                raise ValueError('Unexpected receipt.')
            return {'updated': True, 'version': result['version']}
        except HTTPError as error:
            if error.code not in (429, 502, 503, 504) or attempt == 2:
                raise ValueError('Submission rejected. Check the endpoint, token, expiry and rate limit.') from None
        except (URLError, TimeoutError, OSError):
            if attempt == 2:
                raise ValueError('Could not reach the HTTPS relay.') from None
        time.sleep(attempt + 1)


def main():
    try:
        markdown = sys.stdin.buffer.read(100001).decode('utf-8')
        result = send(markdown, os.environ.get('STUDENT_RELAY_ENDPOINT', ''), os.environ.get('STUDENT_RELAY_TOKEN', ''), os.environ.get('STUDENT_RELAY_REQUEST_ID') or None)
        print(json.dumps(result))
        return 0
    except (ValueError, KeyError, TypeError):
        print('Could not send encrypted response. Check the updated helper/dependency, HTTPS endpoint, public key, token, expiry, content size and network access. No plaintext fallback was sent.', file=sys.stderr)
        return 1

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