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

# %% auto #0
__all__ = ['eps', 'cf_api_url', 'build_cf_eps', 'CloudflareApi']

# %% ../nbs/00_core.ipynb #2b79e1ee
from fastcore.utils import *

import httpx
from inspect import Parameter,Signature
from jsonref import loads as jloads

# %% ../nbs/00_core.ipynb #a7114440
_verbs = frozenset('get post put patch delete head options'.split())

# %% ../nbs/00_core.ipynb #73d561f3
_filler = {'for','a','the','an','in','of'}

def _tag2res(tag):
    parts = tag.lower().replace('-','_').replace('(','').replace(')','').split()
    return '_'.join(p for p in parts if p not in _filler)

# %% ../nbs/00_core.ipynb #33654c70
def _mk_param(name, **kwargs):
    kwargs.pop('description', None)
    name = name.replace('.', '_').replace('-', '_')
    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)

def _deets(k,v):
    'Extracts the type and default value from a schema'
    return {'name': k, 'description': v.get('description', ''),
        'annotation': _lu_type[v.get('type', 'NA')], 'default' : v.get('default', None)}

_lu_type = dict(zip(
    'NA string object array boolean number integer'.split(),
    map(PrettyString,'object str dict list bool int int'.split())
))

def _cf_info(desc):
    data = nested_idx(desc, *'requestBody content application/json schema properties'.split()) or {}
    data = [_deets(*o) for o in data.items()]
    params = desc.get('parameters', [])
    qparams = [dict(name=p['name'], description=p.get('description','')) for p in params if p.get('in')=='query']
    return dict(data=data, op_id=desc.get('operationId',''), qparams=qparams, summary=desc.get('summary',''),
                tags=desc.get('tags',[]))

# %% ../nbs/00_core.ipynb #624fd59f
def build_cf_eps():
    _pkg = Path(__file__).parent if '__file__' in globals() else Path('../fastcflare')
    spec = jloads((_pkg/'openapi.json').read_text())
    return [dict(path=p, verb=v, **_cf_info(desc))
            for p,vs in spec['paths'].items() for v,desc in vs.items() if v in _verbs]

# %% ../nbs/00_core.ipynb #9ff83c1d
_pkg = Path(__file__).parent if '__file__' in globals() else Path('../fastcflare')
eps = L(eval((_pkg/'eps.py').read_text()))

# %% ../nbs/00_core.ipynb #f12db393
def _dedup_verbs(verbs):
    seen,out = {},[]
    for v in verbs:
        key = f'{v.res}.{v.name}'
        if key in seen:
            prev = seen[key]
            if prev:
                prev.name = f'{prev.name}_{prev.pparams[0]}' if prev.pparams else prev.name
                seen[key] = None
            v.name = f'{v.name}_{v.pparams[0]}' if v.pparams else v.name
        else: seen[key] = v
        out.append(v)
    return out

# %% ../nbs/00_core.ipynb #fd2d07d1
def _op2name(op_id, tag, path='', verb=''):
    nm = camel2snake(op_id).replace('-', '_')
    nm = re.sub(r'_0_', '_', nm).strip('_')
    res_parts = _tag2res(tag).split('_')
    nm_parts = nm.split('_')
    while res_parts and nm_parts:
        if nm_parts[0] in _filler: nm_parts.pop(0); continue
        if nm_parts[0].rstrip('s') == res_parts[0].rstrip('s'): res_parts.pop(0); nm_parts.pop(0)
        else: break
    nm = '_'.join(nm_parts)
    if not nm:
        seg = path.rstrip('/').rsplit('/', 1)[-1].replace('-','_').strip('{}')
        nm = f'{verb}_{seg}'
    return nm

# %% ../nbs/00_core.ipynb #45c1438f
class _CfVerbGroup:
    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/00_core.ipynb #322aef28
class _CfVerb:
    def __init__(self, path, verb, op_id, summary, qparams, data, tags, client):
        tag = tags[0] if tags else 'misc'
        self.res = _tag2res(tag)
        self.name = _op2name(op_id, tag, path, verb)
        path, *_ = partial_format(path)
        self.pparams = stringfmt_names(path)
        store_attr()

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

    def __call__(self, *args, **kwargs):
        flds = self.pparams + [p['name'] for p in self.qparams] + [p['name'] for p in self.data]
        for a,b in zip(args, flds): kwargs[b] = a
        route_p,query_p,data_p = [{p:kwargs.pop(p) for p in list(o) if p in kwargs}
            for o in (self.pparams, [p['name'] for p in self.qparams], [p['name'] for p in self.data])]
        if kwargs and self.verb in ('post','put','patch'): data_p.update(kwargs)
        return self.client(self.path, self.verb, route=route_p, query=query_p, data=data_p)

# %% ../nbs/00_core.ipynb #e5c73b99
cf_api_url = 'https://api.cloudflare.com/client/v4'

# %% ../nbs/00_core.ipynb #dd30d894
class CloudflareApi:
    "Cloudflare API client supporting both user and account tokens"
    def __init__(self, token=None, account_id=None, email=None, api_key=None, base_url=cf_api_url):
        if not token and not api_key: token = os.getenv('CF_API_TOKEN')
        if api_key and not email: email = os.getenv('CF_API_EMAIL')
        store_attr()
        if api_key: self.hdrs = {'X-Auth-Email': email, 'X-Auth-Key': api_key, 'Content-Type': 'application/json'}
        else: self.hdrs = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}
        verbs = _dedup_verbs(eps.map(lambda x: _CfVerb(**x, client=self)))
        self.groups = {k: _CfVerbGroup(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(**route)
        query = dict(query or {})
        if self.account_id: query['account.id'] = self.account_id
        return path, verb or 'GET', headers, query or None, data

    def __call__(self, path, verb=None, headers=None, route=None, query=None, data=None):
        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, json=data)
        res = dict2obj(resp.json())
        if not res.success: raise Exception(f'{verb.upper()} /{path}: {res.errors}')
        return res

    def verify(self):
        if self.account_id: return self(f'/accounts/{self.account_id}/tokens/verify')
        return self('/user/tokens/verify')

    def __dir__(self): return super().__dir__() + list(self.groups)
    def _repr_markdown_(self): return "\n".join(f"- {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))

# %% ../nbs/00_core.ipynb #de443569
@patch
def create_token(self:CloudflareApi, doms, perm_names, name, grp='account.zone.'):
    "Create a scoped Cloudflare API token for given domains and permission names"
    pref = 'com.cloudflare.api.'+grp
    pgs = self.user_api_tokens.permission_groups_list_permission_groups().result
    perms = [dict(id=p.id) for p in pgs if p.name in perm_names]
    zids = {f'{pref}{self.zone.get(name=d).result[0].id}':'*' for d in doms}
    return self.user_api_tokens.create_token(name=name, policies=[dict(effect='allow', resources=zids, permission_groups=perms)])
