"""Basic API for Caddy"""

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

# %% auto 0
__all__ = ['automation_path', 'srvs_path', 'rts_path', 'get_id', 'get_path', 'gid', 'has_id', 'gcfg', 'has_path', 'pid', 'pcfg',
           'nested_setdict', 'path2keys', 'keys2path', 'nested_setcfg', 'init_path', 'get_acme_config',
           'add_tls_internal_config', 'add_acme_config', 'init_routes', 'setup_pki_trust', 'setup_caddy', 'add_route',
           'del_id', 'add_reverse_proxy', 'add_wildcard_route', 'add_sub_reverse_proxy']

# %% ../nbs/00_core.ipynb 3
import os, subprocess, httpx, json
from fastcore.utils import *
from httpx import HTTPStatusError, get as xget, post as xpost, patch as xpatch, put as xput, delete as xdelete, head as xhead
from typing import Sequence

# %% ../nbs/00_core.ipynb 5
def get_id(path):
    "Get a ID full URL from a path"
    if path[0 ]!='/': path = '/'+path
    if path[-1]!='/': path = path+'/'
    return f'http://localhost:2019/id{path}'

# %% ../nbs/00_core.ipynb 8
def get_path(path):
    "Get a config full URL from a path"
    if path[0 ]!='/': path = '/'+path
    if path[-1]!='/': path = path+'/'
    return f'http://localhost:2019/config{path}'

# %% ../nbs/00_core.ipynb 10
def gid(path='/'):
    "Gets the id at `path`"
    response = xget(get_id(path))
    response.raise_for_status()
    return dict2obj(response.json())

# %% ../nbs/00_core.ipynb 11
def has_id(id):
    "Check if `id` is set up"
    try: gid(id)
    except HTTPStatusError: return False
    return True

# %% ../nbs/00_core.ipynb 12
def gcfg(path='/', method='get'):
    "Gets the config at `path`"
    f = getattr(httpx, method)
    response = f(get_path(path))
    response.raise_for_status()
    return dict2obj(response.json())

# %% ../nbs/00_core.ipynb 13
def has_path(path):
    "Check if `path` is set up"
    try: gcfg(path)
    except HTTPStatusError: return False
    return True

# %% ../nbs/00_core.ipynb 15
def pid(d, path='/', method='post'):
    "Puts the config `d` into `path`"
    f = getattr(httpx, method)
    response = f(get_id(path), json=obj2dict(d))
    response.raise_for_status()
    return response.text or None

# %% ../nbs/00_core.ipynb 16
def pcfg(d, path='/', method='post'):
    "Puts the config `d` into `path`"
    f = getattr(httpx, method)
    response = f(get_path(path), json=obj2dict(d))
    try: response.raise_for_status()
    except Exception as e:
        e.add_note(f"Error: '{json.loads(response.text)['error']}'")
        raise
    return response.text or None

# %% ../nbs/00_core.ipynb 18
def nested_setdict(sd, value, *keys):
    "Returns `sd` updated to set `value` at the path `keys`"
    d = sd
    for key in keys[:-1]: d = d.setdefault(key, {})
    d[keys[-1]] = value
    return sd

# %% ../nbs/00_core.ipynb 20
def path2keys(path):
    "Split `path` by '/' into a list"
    return path.strip('/').split('/')

# %% ../nbs/00_core.ipynb 22
def keys2path(*keys):
    "Join `keys` into a '/' separated path"
    return '/'+'/'.join(keys)

# %% ../nbs/00_core.ipynb 24
def nested_setcfg(value, *keys):
    d = nested_setdict(gcfg(), value, *keys)
    return pcfg(d)

# %% ../nbs/00_core.ipynb 25
def init_path(path, skip=0):
    sp = []
    for i,p in enumerate(path2keys(path)):
        sp.append(p)
        if i<skip: continue
        pcfg({}, keys2path(*sp))

# %% ../nbs/00_core.ipynb 28
automation_path = '/apps/tls/automation'
def get_acme_config(token):
    prov = { "provider": { "name": "cloudflare", "api_token": token } }
    return { "module": "acme", "challenges": { "dns": prov } }

# %% ../nbs/00_core.ipynb 29
def add_tls_internal_config():
    if has_path(automation_path): return    
    pcfg({})
    init_path(automation_path)
    val = [{"issuers": [{"module": "internal"}]}]
    pcfg(val, automation_path+'/policies')

# %% ../nbs/00_core.ipynb 30
def add_acme_config(cf_token):
    if has_path(automation_path): return
    pcfg({})
    init_path(automation_path)
    val = [get_acme_config(cf_token)]
    pcfg([{'issuers':val}], automation_path+'/policies')

# %% ../nbs/00_core.ipynb 34
srvs_path = '/apps/http/servers'
rts_path = srvs_path+'/srv0/routes'

# %% ../nbs/00_core.ipynb 35
def init_routes(srv_name='srv0', skip=1):
    "Create basic http server/routes config"
    if has_path(srvs_path): return
    init_path(srvs_path, skip=skip)
    ir = {'listen': [':80', ':443'], 'routes': [], 'protocols': ['h1', 'h2']}
    pcfg(ir, f"{srvs_path}/{srv_name}")

# %% ../nbs/00_core.ipynb 37
def setup_pki_trust(install_trust):
    "Configure PKI certificate authority trust installation"
    if install_trust is None: return
    pki_path = '/apps/pki/certificate_authorities/local'
    init_path(pki_path, skip=1)
    pcfg({"install_trust": install_trust}, pki_path)

# %% ../nbs/00_core.ipynb 38
def setup_caddy(
        cf_token=None, # Cloudflare API token
        srv_name='srv0', # Server name in the Caddyfile
        local:bool=False, # Whether or not this is for localdev or deployment
        install_trust:bool=None): # Install trust store?
    "Create SSL config and HTTP app skeleton"
    if local: add_tls_internal_config()
    else: add_acme_config(cf_token)
    setup_pki_trust(install_trust)
    init_routes(srv_name)

# %% ../nbs/00_core.ipynb 41
def add_route(route):
    "Add `route` dict to config"
    return pcfg(route, rts_path)

# %% ../nbs/00_core.ipynb 42
def del_id(id):
    "Delete route for `id` (e.g. a host)"
    xdelete(get_id(id))

# %% ../nbs/00_core.ipynb 44
def add_reverse_proxy(from_host, to_url):
    "Create a reverse proxy handler"
    if has_id(from_host): del_id(from_host)
    route = {
        "handle": [{ "handler": "reverse_proxy",
                    "upstreams": [{"dial": to_url}] }],
        "match": [{"host": [from_host]}],
        "@id": from_host,
        "terminal": True
    }
    add_route(route)

# %% ../nbs/00_core.ipynb 48
def add_wildcard_route(domain):
    "Add a wildcard subdomain"
    route = {
        "match": [{"host": [f"*.{domain}"]}],
        "handle": [
            { "handler": "subroute", "routes": [] }
        ],
        "@id": f"wildcard-{domain}",
        "terminal": True
    }
    add_route(route)

# %% ../nbs/00_core.ipynb 50
def add_sub_reverse_proxy(
        domain,
        subdomain,
        port:str|int|Sequence, # A single port or list of ports
        host='localhost'
    ):
    "Add a reverse proxy to a wildcard subdomain supporting multiple ports"
    wildcard_id = f"wildcard-{domain}"
    route_id = f"{subdomain}.{domain}"
    if isinstance(port, (int,str)): port = [port]
    upstreams = [{"dial": f"{host}:{p}"} for p in port]
    new_route = {
        "@id": route_id,
        "match": [{"host": [route_id]}],
        "handle": [{ "handler": "reverse_proxy", "upstreams": upstreams }]
    }
    pid([new_route], f"{wildcard_id}/handle/0/routes/...")
