"""WhatsOS OTP server-side Python client. Python 3.10+, standard library only."""
import json
import re
import urllib.error
import urllib.request

class WhatsosOtpError(Exception):
    def __init__(self, status, payload, retry_after=None):
        self.status = status
        self.code = payload.get('error', {}).get('code', 'REQUEST_FAILED')
        self.request_id = payload.get('request_id')
        self.retry_after = retry_after
        super().__init__(payload.get('error', {}).get('message', 'OTP request failed'))

class _NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

class WhatsosOtp:
    def __init__(self, api_key, timeout=15):
        if not re.fullmatch(r'wotp_(test|live)_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+', api_key):
            raise ValueError('Invalid OTP API key format')
        self._key, self._timeout = api_key, timeout
        self._http = urllib.request.build_opener(_NoRedirect())

    @staticmethod
    def _id(value):
        if not re.fullmatch(r'ver_[A-Za-z0-9_-]+', value):
            raise ValueError('Invalid verification ID')
        return value

    def _request(self, path, body=None, request_id=None):
        headers = {'Authorization': 'Bearer ' + self._key, 'Content-Type': 'application/json'}
        if body is not None:
            if not isinstance(request_id, str) or not re.fullmatch(r'[A-Za-z0-9_.:-]{8,128}', request_id):
                raise ValueError('A stable request_id is required for mutations')
            headers['Idempotency-Key'] = request_id
        request = urllib.request.Request('https://whatsos.com/api/v1/otp' + path,
            data=None if body is None else json.dumps(body).encode(), headers=headers,
            method='GET' if body is None else 'POST')
        try:
            with self._http.open(request, timeout=self._timeout) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            try:
                payload = json.loads(error.read())
            except (ValueError, UnicodeError):
                payload = {'error': {'code': 'HTTP_ERROR', 'message': 'OTP request failed'}}
            raise WhatsosOtpError(error.code, payload, error.headers.get('Retry-After')) from None
        # Transport failures propagate. Retry the same operation with the same request_id.

    def send(self, to, purpose, request_id, reference='', locale='en_US'):
        return self._request('/verifications', {'to': to, 'purpose': purpose, 'reference': reference, 'locale': locale}, request_id)

    def get(self, verification_id):
        return self._request('/verifications/' + self._id(verification_id))

    def check(self, verification_id, code, request_id):
        return self._request('/verifications/' + self._id(verification_id) + '/check', {'code': code}, request_id)

    def resend(self, verification_id, request_id):
        return self._request('/verifications/' + self._id(verification_id) + '/resend', {}, request_id)

    def cancel(self, verification_id, request_id):
        return self._request('/verifications/' + self._id(verification_id) + '/cancel', {}, request_id)
