# command line interface  # noqa:D100
from __future__ import annotations

import asyncclick as click

from moat.util import NotGiven, P, Path, PathLongener, yload, yprint


@click.group()  # pylint: disable=undefined-variable
async def cli():
    """Manage codecs and converters. Usage: … codec …"""
    pass


@cli.command()
@click.option("-e", "--encode", type=click.File(mode="w", lazy=True), help="Save the encoder here")
@click.option("-d", "--decode", type=click.File(mode="w", lazy=True), help="Save the decoder here")
@click.option("-s", "--script", type=click.File(mode="w", lazy=True), help="Save the data here")
@click.argument("path", nargs=1)
@click.pass_obj
async def get(obj, path, script, encode, decode):
    """Read type information"""
    path = P(path)
    if not len(path):
        raise click.UsageError("You need a non-empty path.")
    res = await obj.client._request(  # noqa:SLF001
        action="get_internal",
        path=Path("codec") + path,
        iter=False,
        nchain=obj.meta,
    )
    if encode and res.get("encode", None) is not None:
        encode.write(res.pop("encode"))
    if decode and res.get("decode", None) is not None:
        decode.write(res.pop("decode"))

    if not obj.meta:
        res = res.value
    yprint(res, stream=script or obj.stdout)


@cli.command(name="list")
@click.pass_obj
@click.argument("path", nargs=1)
async def list_(obj, path):
    """List type information entries"""
    res = await obj.client._request(  # noqa:SLF001
        action="get_tree_internal",
        path=Path("codec") + P(path),
        iter=True,
        nchain=obj.meta,
    )
    pl = PathLongener(())
    async for r in res:
        pl(r)
        print(" ".join(str(x) for x in r.path), file=obj.stdout)


@cli.command("set")
@click.option("-e", "--encode", type=click.File(mode="r"), help="File with the encoder")
@click.option("-d", "--decode", type=click.File(mode="r"), help="File with the decoder")
@click.option("-D", "--data", type=click.File(mode="r"), help="File with the rest")
@click.option("-i", "--in", "in_", nargs=2, multiple=True, help="Decoding sample")
@click.option("-o", "--out", nargs=2, multiple=True, help="Encoding sample")
@click.argument("path", nargs=1)
@click.pass_obj
async def set_(obj, path, encode, decode, data, in_, out):
    """Save codec information"""
    path = P(path)
    if not len(path):
        raise click.UsageError("You need a non-empty path.")

    if data:
        msg = yload(data)
    else:
        msg = {}
    chain = NotGiven
    if "value" in msg:
        chain = msg.get("chain", NotGiven)
        msg = msg["value"]

    if "encode" in msg:
        if encode:
            raise click.UsageError("Duplicate encode script")
    else:
        if not encode:
            raise click.UsageError("Missing encode script")
        msg["encode"] = encode.read()
    if "decode" in msg:
        if decode:
            raise click.UsageError("Duplicate decode script")
    else:
        if not decode:
            raise click.UsageError("Missing decode script")
        msg["decode"] = decode.read()
    if in_:
        msg["in"] = [(eval(a), eval(b)) for a, b in in_]  # pylint: disable=eval-used
    if out:
        msg["out"] = [(eval(a), eval(b)) for a, b in out]  # pylint: disable=eval-used

    if not msg["in"]:
        raise click.UsageError("Missing decode tests")
    if not msg["out"]:
        raise click.UsageError("Missing encode tests")

    res = await obj.client._request(  # noqa:SLF001
        action="set_internal",
        value=msg,
        path=Path("codec") + path,
        iter=False,
        nchain=obj.meta,
        **({} if chain is NotGiven else {"chain": chain}),
    )
    if obj.meta:
        yprint(res, stream=obj.stdout)


@cli.command()
@click.option("-c", "--codec", type=P, help="Codec to link to.")
@click.option("-d", "--delete", is_flag=True, help="Use to delete this converter.")
@click.option(
    "-l",
    "--list",
    "list_",
    is_flag=True,
    help="Use to list this converter; '-' to list all.",
)
@click.argument("name", nargs=1)
@click.argument("path", type=P, nargs=1)
@click.pass_obj
async def convert(obj, path, codec, name, delete, list_):
    """Match a codec to a path (read, if no codec given)"""
    path = P(path)
    if delete and list_:
        raise click.UsageError("You can't both list and delete a path.")
    if not len(path) and not list_:
        raise click.UsageError("You need a non-empty path.")
    if codec and delete:
        raise click.UsageError("You can't both set and delete a path.")

    if list_:
        if name == "-":
            if len(path):
                raise click.UsageError("You can't use a path here.")
            res = await obj.client._request(  # noqa:SLF001
                action="enum_internal",
                path=Path("conv"),
                iter=False,
                nchain=0,
                empty=True,
            )
            for r in res.result:
                print(r, file=obj.stdout)

        else:
            res = await obj.client._request(  # noqa:SLF001
                action="get_tree_internal",
                path=Path("conv", name) + path,
                iter=True,
                nchain=obj.meta,
            )
            pl = PathLongener(())
            async for r in res:
                pl(r)
                try:
                    print(f"{r.path} : {Path.build(r.value['codec'])}", file=obj.stdout)
                except Exception as e:
                    print(f"{Path(r.path)} {e!r}", file=obj.stdout)

        return
    if delete:
        res = await obj.client._request(action="delete_internal", path=Path("conv", name) + path)  # noqa:SLF001
    else:
        msg = {"codec": codec}
        res = await obj.client._request(  # noqa:SLF001
            action="set_internal",
            value=msg,
            path=Path("conv", name) + path,
            iter=False,
            nchain=obj.meta,
        )
    if obj.meta:
        yprint(res, stream=obj.stdout)
    elif type or delete:
        print(res.tock, file=obj.stdout)
    else:
        print(" ".join(res.type), file=obj.stdout)
