"""This module provides specializes the Python ConfigParser class, pre-defining
a set of config settings for zeek-client and includes ways to override these
from the environment and the command line.
"""

import configparser
import os
import shlex

from .consts import CONFIG_FILE
from .logs import LOG


class Config(configparser.ConfigParser):
    """zeek-client configuration settings.

    A specialized ConfigParser that hardwires defaults for select values.
    Three levels of overrides apply, if provided:

    (1) first, the config file, if available

    (2) the ZEEK_CLIENT_CONFIG_SETTINGS environment variable may contain a
        series of <section.key>=<val> assignments

    (3) Any --set <section.key>=<val> arguments apply final overrides
    """

    def __init__(self):
        super().__init__()
        self.reset()

    def reset(self):
        self.read_dict(
            {
                "client": {
                    # The default timeout for request state is 15 seconds on the
                    # Zeek side, so by making it larger here we ensure that timeout
                    # events can fire & propagate in Zeek before we give up here.
                    "request_timeout_secs": 20,
                    # Successful peering requires a successful WebSocket connection
                    # to the controller and the successful exchange of peering
                    # handshake and response. We retry both, counting connection as
                    # well as handshake attempts toward this total:
                    "peering_attempts": 10,
                    # How long the client's Broker's endpoint should wait internally
                    # until it retries a peering upon connection or when the
                    # connection goes away. Its default is 10 seconds; we dial that
                    # down to be more interactive.
                    "peering_retry_delay_secs": 1.0,
                    # The way zeek-client reports informational messages on stderr
                    "rich_logging_format": False,
                    # Whether we pretty-print JSON output by default.
                    "pretty_json": True,
                    # Default output verbosity level:
                    #   0   permits errors
                    #   1   also warnings
                    #   2   also informational messages
                    #   3   also debug messages
                    #   4+  no additional effect
                    "verbosity": 0,
                },
                "controller": {
                    # Default host name/address where we contact the controller.
                    "host": "127.0.0.1",
                    # Default WebSocket port of the controller.
                    "port": 2149,
                },
                "ssl": {
                    # These settings control the security settings of the connection
                    # to the controller. They mirror Broker's approach and naming:
                    # by default, SSL is active, but unvalidated. Providing
                    # certificate, private key, and possibly CA & passphrase secure
                    # the connection properly.  Compare to Zeek's Broker framework.
                    # Whether to use SSL at all. Disabling this yields plaintext
                    # communication. This mirrors Broker::disable_ssl on the Zeek
                    # side.
                    "disable": False,
                    # Path to a file containing a X.509 certificate in PEM format.
                    "certificate": "",
                    # Path to a file containing the private key for the certificate,
                    # in PEM format.
                    "keyfile": "",
                    # Path to a file containing concatenated, trusted certificates,
                    # in PEM format.
                    "cafile": "",
                    # Path to an OpenSSL-style directory of trusted certificates.
                    "capath": "",
                    # A passphrase to decrypt the private key, if required.
                    "passphrase": "",
                },
            },
        )

    def update_from_file(self, config_file=CONFIG_FILE):
        self.read(config_file)

    def update_from_env(self):
        for item in shlex.split(os.getenv("ZEEK_CLIENT_CONFIG_SETTINGS") or ""):
            try:
                self.apply(item)
            except ValueError:
                LOG.error(
                    'config item "%s" in ZEEK_CLIENT_CONFIG_SETTINGS '
                    "invalid. Please use <section.key>=<val>.",
                    item,
                )

    def update_from_args(self, args):
        for item in args.set:
            try:
                self.apply(item)
            except ValueError:
                LOG.error(
                    'config item "%s" invalid. Please use <section.key>=<val>.',
                    item,
                )

        # The `--controller` argument is a shortcut for two `--set` arguments that
        # set controller host and port, so update these manually:
        if args.controller:
            if ":" not in args.controller:
                host = args.controller
                port = ""
            else:
                (host, _, port) = args.controller.rpartition(":")
                if host.count(":") >= 1:
                    # We likely have an IPv6 address
                    if not host.startswith("["):
                        raise ValueError(
                            "IPv6 addresses must be surrounded by brackets: [<ipv6>]:<port>"
                        )
                    if port.endswith("]"):
                        host = host + ":" + port
                        port = ""

            if port == "":
                # It's just a hostname
                self.set("controller", "host", host)
            elif host == "":
                # It's just a port (as ":<port>")
                self.set("controller", "port", port)
            else:
                self.set("controller", "host", host)
                self.set("controller", "port", port)

        # --verbose/-v/-vvv etc set a numeric verbosity level:
        if args.verbose:
            self.set("client", "verbosity", str(args.verbose))

    def apply(self, item):
        """This is equivalent to set(), but works via a single <section.key>=<val> string."""
        try:
            identifier, val = item.split("=", 1)
            section, key = identifier.split(".", 1)
            if not self.has_section(section):
                self.add_section(section)
            self.set(section, key, val)
        except ValueError as err:
            raise ValueError(f'config item "{item}" invalid') from err

    def completer(self, **_kwargs):
        """A completer suitable for argcomplete."""
        ret = []

        for section in self.sections():
            for key, val in self.items(section):
                ret.append(section + "." + key + "=" + val)

        return sorted(ret)


CONFIG = Config()
