"""Bounded JSON and secret handling, shared by the Sidecar modules."""
import json
import re
from pathlib import Path

MAX_BODY = 64 * 1024
TOKEN = re.compile(r'[!-~]{16,8192}\Z')


class BoundaryError(Exception):
    def __init__(self, code, status=503):
        super().__init__(code)
        self.code, self.status = code, status


def secret(path):
    p = Path(path)
    if not p.is_absolute() or p.is_symlink() or not p.is_file():
        raise ValueError('SECRET_FILE_INVALID')
    with p.open('rb') as f:
        raw = f.read(8193)
    value = raw.decode('utf-8').rstrip('\r\n')
    if len(raw) > 8192 or not TOKEN.fullmatch(value):
        raise ValueError('SECRET_VALUE_INVALID')
    return value


def bearer(headers):
    values = headers.getlist('authorization') if hasattr(headers, 'getlist') else headers.get_list('authorization') if hasattr(headers, 'get_list') else [headers.get('authorization', '')]
    if len(values) != 1 or not values[0].startswith('Bearer '):
        raise BoundaryError('TOKEN_INVALID', 401)
    token = values[0][7:]
    if not TOKEN.fullmatch(token):
        raise BoundaryError('TOKEN_INVALID', 401)
    return token


def loads(raw):
    if len(raw) > MAX_BODY:
        raise BoundaryError('BODY_TOO_LARGE', 413)
    def pairs(items):
        value = {}
        for k, v in items:
            if k in value:
                raise ValueError('duplicate key')
            value[k] = v
        return value
    def invalid(_):
        raise ValueError('nonfinite')
    try:
        value = json.loads(raw, object_pairs_hook=pairs, parse_constant=invalid)
        if not isinstance(value, dict):
            raise ValueError('object required')
        return value
    except (ValueError, UnicodeError, TypeError, RecursionError):
        raise BoundaryError('INVALID_JSON', 422) from None


async def read_body(request):
    raw = bytearray()
    async for chunk in request.stream():
        if len(raw) + len(chunk) > MAX_BODY:
            raise BoundaryError('BODY_TOO_LARGE', 413)
        raw.extend(chunk)
    return bytes(raw)
