"""Traefik implementation

Custom proxy implementations can subclass :class:`Proxy`
and register in JupyterHub config:

.. sourcecode:: python

    from mymodule import MyProxy
    c.JupyterHub.proxy_class = MyProxy

Route Specification:

- A routespec is a URL prefix ([host]/path/), e.g.
  'host.tld/path/' for host-based routing or '/path/' for default routing.
- Route paths should be normalized to always start and end with '/'
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import base64
import string
from urllib.parse import urlparse

from traitlets import Any, Unicode, default

from .kv_proxy import TKvProxy


class TraefikConsulProxy(TKvProxy):
    """JupyterHub Proxy implementation using traefik and Consul"""

    provider_name = "consul"

    # Consul doesn't accept keys containing // or starting with / so we have to escape them
    key_safe_chars = string.ascii_letters + string.digits + "!@#$%^&*();<>-.+?:"

    consul_client_ca_cert = Unicode(
        config=True,
        allow_none=True,
        default_value=None,
        help="""Consul client root certificates""",
    )

    consul_url = Unicode(
        "http://127.0.0.1:8500",
        config=True,
        help="URL for the consul endpoint.",
    )
    consul_username = Unicode(
        "",
        config=True,
        help="Usrname for accessing consul.",
    )
    consul_password = Unicode(
        "",
        config=True,
        help="Password or token for accessing consul.",
    )

    kv_url = Unicode("DEPRECATED", config=True).tag(
        deprecated_in="1.0",
        deprecated_for="consul_url",
    )
    kv_username = Unicode("DEPRECATED", config=True).tag(
        deprecated_in="1.0",
        deprecated_for="consul_username",
    )
    kv_password = Unicode("DEPRECATED", config=True).tag(
        deprecated_in="1.0",
        deprecated_for="consul_password",
    )

    consul = Any()

    @default("consul")
    def _default_client(self):
        try:
            import consul.aio
        except ImportError:
            raise ImportError(
                "Please install python-consul2 package to use traefik-proxy with consul"
            )
        consul_service = urlparse(self.consul_url)
        kwargs = {
            "host": consul_service.hostname,
            "port": consul_service.port,
            "cert": self.consul_client_ca_cert,
        }
        if self.consul_password:
            kwargs.update({"token": self.consul_password})
        return consul.aio.Consul(**kwargs)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.log.warning(
            "Using traefik with consul is deprecated in jupyterhub-traefik-proxy 1.0 due to lack of support for the python-consul2 API client. Use etcd instead."
        )

    def _setup_traefik_static_config(self):
        provider_config = {
            "consul": {
                "rootKey": self.kv_traefik_prefix,
                "endpoints": [urlparse(self.consul_url).netloc],
            }
        }

        # FIXME: Same with the tls info
        if self.consul_client_ca_cert:
            provider_config["consul"]["tls"] = {"ca": self.consul_client_ca_cert}

        self.static_config.update({"providers": provider_config})
        return super()._setup_traefik_static_config()

    def _start_traefik(self):
        if self.consul_password:
            if self.consul_username:
                self.traefik_env.setdefault(
                    "CONSUL_HTTP_AUTH", f"{self.consul_username}:{self.consul_password}"
                )
            else:
                self.traefik_env.setdefault("CONSUL_HTTP_TOKEN", self.consul_password)
        super()._start_traefik()

    async def _kv_atomic_set(self, to_set):
        payload = []

        def append_payload(key, val):
            payload.append(
                {
                    "KV": {
                        "Verb": "set",
                        "Key": key,
                        "Value": base64.b64encode(val.encode()).decode(),
                    }
                }
            )

        for k, v in to_set.items():
            append_payload(k, v)

        try:
            await self.consul.txn.put(payload=payload)
        except Exception:
            self.log.exception("Error uploading payload to KV store!")
        else:
            self.log.debug("Successfully uploaded payload to KV store")

    async def _kv_atomic_delete(self, *to_delete):
        payload = []

        for key in to_delete:
            if key.endswith(self.kv_separator):
                verb = "delete-tree"
            else:
                verb = "delete"
            payload.append(
                {
                    "KV": {"Verb": verb, "Key": key},
                }
            )
        status, response = await self.consul.txn.put(payload=payload)
        # check response?

    async def _kv_get_tree(self, prefix):
        response = await self.consul.txn.put(
            payload=[
                {
                    "KV": {
                        "Verb": "get-tree",
                        "Key": prefix,
                    }
                }
            ]
        )
        kv_list = [
            (
                item["KV"]["Key"],
                base64.b64decode(item["KV"]["Value"] or '').decode("utf8"),
            )
            for item in response["Results"]
        ]
        return self.unflatten_dict_from_kv(kv_list, root_key=prefix)
