"""
R/W access to configuration data.
"""

from __future__ import annotations

from moat.util import ExpKeyError, NotGiven, attrdict, enc_part, get_part, to_attrdict
from moat.lib.micro import log as log
from moat.lib.rpc import BaseCmd


class Cmd(BaseCmd):
    """
    Subsystem to handle config data.

    This app serves the config of the parent subcommand.
    """

    doc = dict(_c=dict(_d="Config read/write"))

    def __init__(self, cfg):
        super().__init__(cfg)
        self.repeats = {}

    doc_r = dict(_d="read cfg", _r="parts:dict[str,Any]:Config data")

    async def stream_r(self, msg):
        """
        Read (part of) the configuration.

        As configuration data are frequently too large to transmit in one
        go, this code interrogates them step-by-step.

        Args:
            p: path. An empty path is the root.

        If the accessed item is a dict, return data consists of a dict
        (simple keys and their values) and a list (keys for complex
        values).

        Likewise, the data for a list is a list with "simple" elements
        (complex data replaced by `None`), plus a list with the offsets of
        complex items that should be retrieved recursively.
        """
        p = msg.get("p", ())
        try:
            res = enc_part(get_part(self.root.cfg, p))
            if isinstance(res, (list, tuple)):
                await msg.result(*res)
            else:
                await msg.result(res)
        except KeyError as exc:
            raise ExpKeyError(*exc.args) from None

    doc_c = dict(_d="write cfg", p="Path:subpart", d="any:Data")

    async def cmd_c(self, p, d=NotGiven, keep: bool = False):
        """
        Online configuration mangling.

        As configuration data are frequently too large to transmit in one
        go, this code updates them step-by-step.

        Args:
            p: data path. It cannot be empty. Destinations are
               autogenerated. A path element of `None`, if last, appends
               to a list.

            d: data replacing the destination. `NotGiven` (or omitting the
               parameter) deletes.

        There is no way to write the current config to the file system.
        You can assemble it on the server and write using app.fs, or you
        can configure a "safe" skeleton setup and update it online after
        booting.
        """
        cur = self.root.cfg
        if not p:
            raise ValueError("NoPath")
        for pp in p[:-1]:
            try:
                cur = cur[pp]
            except KeyError:
                cur[pp] = attrdict()
                cur = cur[pp]
            except (IndexError, TypeError):
                if pp is not None and len(cur) != pp:
                    raise
                cur.append(attrdict())
                cur = cur[-1]
        # log("CFG_W %r %r %r", cur, p, d)
        k = p[-1]
        d = to_attrdict(d)
        if d is NotGiven and not keep:
            try:
                del cur[k]
            except KeyError:
                pass
        elif isinstance(cur, list) and k is None:
            cur.append(d)
        else:
            try:
                cur[k] = d
            except IndexError:
                if len(cur) != k:
                    raise
                cur.append(d)

        self.root.cfg_updated(cur)

    doc_x = dict(_d="activate new config")

    async def cmd_x(self):
        """
        Activate the new config.
        """
        await self.root.reload()
