"""Standard-library client for the ParlayMarket Agent API. No automatic mutation retries."""
import json
import hashlib
import hmac
import time
import math
from email.utils import parsedate_to_datetime
from urllib.request import Request, build_opener, HTTPRedirectHandler
from urllib.error import HTTPError

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

def verify_webhook(secret, timestamp, signature, body, now=None, max_age_seconds=300):
    try:
        if abs((time.time() if now is None else now) - int(timestamp)) > max_age_seconds:
            return False
        raw = body if isinstance(body, bytes) else body.encode()
        expected = 'v1=' + hmac.new(secret.encode(), str(timestamp).encode() + b'.' + raw, hashlib.sha256).hexdigest()
        return hmac.compare_digest(expected, signature)
    except (ValueError, TypeError):
        return False


class ParlayMarketError(Exception):
    def __init__(self, payload, status, headers=None):
        error = payload.get('error', {})
        if not isinstance(error, dict):
            error = {'code': 'request_failed', 'message': 'The request failed.'}
        super().__init__(error.get('message', error.get('code', 'Request failed')))
        self.code = error.get('code')
        self.retryable = error.get('retryable', False)
        self.status = status
        self.request_id = payload.get('request_id')
        self.details = error.get('details')
        self.retry_after_seconds = error.get('retry_after_seconds')
        if headers:
            self.request_id = self.request_id or headers.get('x-request-id')
            value = headers.get('retry-after', '')
            if self.retry_after_seconds is None and value.isdigit():
                self.retry_after_seconds = int(value)
            elif self.retry_after_seconds is None and value:
                try:
                    self.retry_after_seconds = max(1, math.ceil(parsedate_to_datetime(value).timestamp() - time.time()))
                except (ValueError, TypeError, OverflowError):
                    pass
        if self.retryable and not self.retry_after_seconds:
            self.retry_after_seconds = 60


class ParlayMarket:
    def __init__(self, token=None, base_url='https://parlaymarket.io', timeout=20):
        self.token, self.base_url, self.timeout = token, base_url.rstrip('/'), timeout

    def call(self, name, arguments=None, idempotency_key=None):
        if name not in TOOLS:
            raise ValueError('Unknown tool')
        headers = {'Content-Type': 'application/json'}
        if self.token:
            headers['Authorization'] = 'Bearer ' + self.token
        if idempotency_key:
            headers['Idempotency-Key'] = idempotency_key
        request = Request(self.base_url + '/api/v1/tools/' + name,
                          data=json.dumps(arguments or {}).encode(), headers=headers, method='POST')
        try:
            with build_opener(_NoRedirect).open(request, timeout=self.timeout) as response:
                try:
                    payload = json.load(response)
                except (ValueError, UnicodeDecodeError):
                    raise ParlayMarketError({'error': {'code': 'invalid_response', 'message': 'The service returned a non-JSON response.'}}, response.status, response.headers) from None
                if not payload.get('ok'):
                    raise ParlayMarketError(payload, response.status)
                return payload['result']
        except HTTPError as error:
            try:
                payload = json.load(error)
            except (ValueError, UnicodeDecodeError):
                payload = {'error': {'code': 'invalid_response', 'message': 'The service returned a non-JSON response.', 'retryable': error.code in (429, 503)}}
            raise ParlayMarketError(payload, error.code, error.headers) from None


TOOLS = ['get_capabilities', 'search_markets', 'get_market', 'get_orderbook', 'create_plan', 'simulate_plan', 'compare_routes', 'request_approval', 'get_authorization_status', 'create_workflow', 'start_workflow', 'pause_workflow', 'cancel_workflow', 'get_workflow', 'get_positions', 'list_events', 'subscribe_events']


def _method(name):
    def call(self, arguments=None, idempotency_key=None):
        return self.call(name, arguments, idempotency_key)
    call.__name__ = name
    return call


for _name in TOOLS:
    setattr(ParlayMarket, _name, _method(_name))
