"""Fill in a module description here"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_core.ipynb.

# %% auto #0
__all__ = ['stripe_api_url', 'names', 'op2nm', 'StripeApi', 'StripeApiAsync', 'paged', 'pages']

# %% ../nbs/01_core.ipynb #7c62c2fe
from fastcore.all import *
from .endpoints import eps
from .spec import docs_url
from inspect import Parameter, Signature, signature
from urllib.parse import quote

import re,httpx

# %% ../nbs/01_core.ipynb #18d69832
stripe_api_url = 'https://api.stripe.com'

# %% ../nbs/01_core.ipynb #c4959d0d
def _mk_param(name, **kwargs):
    kwargs.pop('description', None)
    if name == 'expand': kwargs['annotation'] = list
    return Parameter(name, kind=Parameter.POSITIONAL_OR_KEYWORD, **kwargs)

def _mk_sig(req_args, opt_args, anno_args):
    'req_args are args inside paths such as {account}, opt_args are query params, anno_args are payload params'
    params =  [_mk_param(k) for k in req_args]
    params += [_mk_param(**{'default': None} | p) for p in opt_args or anno_args]
    return Signature(params)

# %% ../nbs/01_core.ipynb #bc382711
def op2nm(op_id):
    'Parse the operation ID to get the resource and name'
    parts = re.findall(r'[A-Z][a-z]*', op_id)
    verb, *nm = [p.lower() for p in parts]
    res, nm = nm[0], nm[1:]
    nm += [verb]
    return res, nm

# %% ../nbs/01_core.ipynb #bcc8fa1e
class _OAPIObj: pass

# %% ../nbs/01_core.ipynb #c27e5cf7
def _flatten_data(data, prefix=''):
    'Flatten a dictionary of data so that it can be used in a request body.'
    result = {}
    for k,v in data.items():
        key = f'{prefix}[{k}]' if prefix else k
        if isinstance(v, dict): result.update(_flatten_data(v, key))
        elif isinstance(v, list): 
            for i,item in enumerate(v):
                if isinstance(item, dict): result.update(_flatten_data(item, f'{key}[{i}]'))
                else: result[f'{key}[{i}]'] = item
        else: result[key] = v
    return result

# %% ../nbs/01_core.ipynb #28c60705
names = lambda x: [o['name'] for o in x]

class _OAPIVerb(_OAPIObj):
    __slots__ = 'path verb res name summary pparams qparams url data doc_url hdrs client __doc__'.split()
    def __init__(self, path, verb, op_id, summary, qparams, data, doc_url, url, hdrs, client, kwargs={}):
        res, name = op2nm(op_id) # custom per oapi spec
        name = '_'.join(name)
        path, *_ = partial_format(path, **kwargs)
        pparams = stringfmt_names(path) # params inside path
        param_docs = '\n'.join(f"    {p['name']}: {p['description']}" for p in qparams + data)
        __doc__ = f"{summary}\n\nParameters:\n{param_docs}" if param_docs else summary
        store_attr()

    def __call__(self, *args, **kwargs):
        'Call the API endpoint with the given arguments'
        flds = self.pparams + names(self.qparams) + names(self.data)
        for a, b in zip(args, flds): kwargs[b] = a
        route_p, query_p, data_p = [{p: kwargs[p] for p in o if p in kwargs}
                                    for o in (self.pparams, names(self.qparams), names(self.data))]
        return self.client(self.path, self.verb, route=route_p, query=query_p, data=data_p)

    def __str__(self): return f'{self.res}.{self.name}{signature(self)}'
    @property
    def __signature__(self): return _mk_sig(self.pparams, self.qparams, self.data)
    __call__.__signature__ = __signature__

    def _repr_markdown_(self):
        return f'[{self.res}.{self.name}]({self.doc_url}){self.__signature__}: *{self.summary}*'
    __repr__ = _repr_markdown_

# %% ../nbs/01_core.ipynb #030c0fda
class _OAPIVerbGroup(_OAPIObj):
    def __init__(self, name, verbs):
        self.name,self.verbs = name,verbs
        for o in verbs: setattr(self, o.name, o)
    def __str__(self): return "\n".join(str(v) for v in self.verbs)
    def _repr_markdown_(self): return "\n".join(f'- {v._repr_markdown_()}' for v in self.verbs)

# %% ../nbs/01_core.ipynb #ff0b6058
class StripeApi:
    def __init__(self, api_key=None, base_url=stripe_api_url):
        if not api_key: api_key = os.getenv('STRIPE_SECRET_KEY', os.getenv('STRIPE_API_KEY'))
        self.api_key,self.base_url = api_key,base_url.strip('/')
        self.hdrs = {'Authorization': f'Bearer {self.api_key}'}
        verbs = L(eps).map(lambda x: _OAPIVerb(**x, url=base_url, hdrs=self.hdrs, client=self))
        self.func_dict = {f'{o.path}:{o.verb.upper()}':o for o in verbs}
        self.groups = {k.replace('-','_'): _OAPIVerbGroup(k,v) for k,v in groupby(verbs, 'res').items()}
    
    def _prep(self, path, verb, headers, route, query, data):
        headers = {**self.hdrs, **(headers or {})}
        path = path.strip('/')
        if route: path = path.format(**{k: quote(str(v)) for k,v in route.items()})
        if query: query = _flatten_data(query)
        if data: data = _flatten_data(data)
        return path, verb or 'GET', headers, query, data

    def __call__(self, path:str, verb:str=None, headers:dict=None, route:dict=None, query:dict=None, data=None):
        'Call the API endpoint with the given arguments'
        path, verb, headers, query, data = self._prep(path, verb, headers, route, query, data)
        resp = httpx.request(verb, f"{self.base_url}/{path}", headers=headers, params=query, data=data).raise_for_status()
        self.recv_hdrs = dict(resp.headers)
        return dict2obj(resp.json())
    
    def __dir__(self): return super().__dir__() + list(self.groups)
    def _repr_markdown_(self): return "\n".join(f"- [{o}]({docs_url + '/' + o})" for o in sorted(self.groups))
    def __getattr__(self,k): return self.groups[k] if 'groups' in vars(self) and k in self.groups else stop(AttributeError(k))
    def __getitem__(self, k):
        "Lookup and call an endpoint by path and verb (which defaults to 'GET')"
        a,b = k if isinstance(k,tuple) else (k,'GET')
        return self.func_dict[f'{a}:{b.upper()}']

# %% ../nbs/01_core.ipynb #7f8a9b7d
class StripeApiAsync(StripeApi):
    async def __call__(self, path:str, verb:str=None, headers:dict=None, route:dict=None, query:dict=None, data=None):
        'Call the API endpoint with the given arguments'
        path, verb, headers, query, data = self._prep(path, verb, headers, route, query, data)
        async with httpx.AsyncClient() as cli:
            resp = (await cli.request(verb, f"{self.base_url}/{path}", headers=headers, params=query, data=data)).raise_for_status()
        self.recv_hdrs = dict(resp.headers)
        return dict2obj(resp.json())

# %% ../nbs/01_core.ipynb #a99be92c
def paged(oper, *args, **kwargs):
    """Iterate through all pages of a Stripe API operation."""
    page = oper(*args, **kwargs)
    yield page
    while page.has_more:
        last_id = page.data[-1].id
        page = oper(*args, starting_after=last_id, **kwargs)
        yield page

# %% ../nbs/01_core.ipynb #77b02d6a
def pages(oper, *args, **kwargs):
    """Retrieve all items from all pages of a Stripe API operation."""
    return L(p for page in paged(oper, *args, **kwargs) for p in page.data)

# %% ../nbs/01_core.ipynb #eec88815
@patch
@delegates(StripeApi().products.get)
def find_product(self:StripeApi, name: str, limit=100, **kwargs):
    'Find a product by name'
    prods = pages(self.products.get, limit=limit, **kwargs)
    return first(prods, lambda p: p.name == name)

# %% ../nbs/01_core.ipynb #39b2d66f
@patch
@delegates(StripeApi().prices.get)
def find_prices(self:StripeApi, product_id: str, limit=100, **kwargs):
    'Find all prices associated with a product id'
    return pages(self.prices.get, limit=limit, **kwargs).filter(lambda p: p.product == product_id)

# %% ../nbs/01_core.ipynb #432001ef
@patch
def priced_product(self:StripeApi, product_name, amount_cents, currency='usd', recurring=None, description=None):
    "Create a product and price if they don't exist"
    prod_params = dict(name=product_name)
    if description: prod_params['description'] = description
    prod = self.find_product(product_name) or self.products.post(**prod_params)
    price_params = dict(product=prod.id, unit_amount=amount_cents, currency=currency)
    if recurring: price_params['recurring'] = recurring
    price = first(self.find_prices(prod.id)) or self.prices.post(**price_params)
    return prod, price

# %% ../nbs/01_core.ipynb #fd1ad7a5
@patch
@delegates(StripeApi().checkout.sessions_post)
def one_time_payment(self:StripeApi, product_name, amount_cents, success_url, cancel_url, currency='usd', quantity=1, **kwargs):
    'Create a simple one-time payment checkout'
    automatic_tax = kwargs.pop('automatic_tax', {'enabled': True})
    _, price = self.priced_product(product_name, amount_cents, currency)
    return self.checkout.sessions_post(mode='payment', line_items=[dict(price=price.id, quantity=quantity)],
                                       automatic_tax=automatic_tax, success_url=success_url, cancel_url=cancel_url, **kwargs)

# %% ../nbs/01_core.ipynb #52aa648c
@patch
@delegates(StripeApi().checkout.sessions_post)
def subscription(self:StripeApi, product_name, amount_cents, success_url, cancel_url,
                 currency='usd', interval='month', **kwargs):
    'Create a simple recurring subscription'
    _, price = self.priced_product(product_name, amount_cents, currency, recurring=dict(interval=interval))
    return self.checkout.sessions_post(mode='subscription', success_url=success_url, cancel_url=cancel_url,
                                       line_items=[dict(price=price.id, quantity=1)], **kwargs)
