import io
import json
import logging
import os
import shutil
import yaml


def install_vmadm(ctx):
    runcmd_prepend_to_path = globals()["runcmd_prepend_to_path"]
    srcdir = globals()["srcdir"]

    # Add the directory with built Rust binaries to the path.
    default_target = os.path.join(srcdir, "target")
    target = os.environ.get("CARGO_TARGET_DIR", default_target)
    runcmd_prepend_to_path(ctx, dirname=os.path.join(target, "debug"))

    # Set permissions on the datadir and its parent. They are 0o700 by default,
    # which means that the libvirt daemon can't access the virtual machine
    # image we create.
    os.chmod(".", 0o711)
    os.chmod("..", 0o711)

    # Create .ssh directory, so that the scenario can put files there later.
    # This can be removed once the Subplot lib/files library creates
    # directories.
    os.mkdir(".ssh")
    os.makedirs("expected/init-test")
    os.mkdir("images")


def ensure_base_image(ctx):
    assert_eq = globals()["assert_eq"]
    assert_ne = globals()["assert_ne"]

    base = os.environ.get("BASE_IMAGE", "")
    _, ext = os.path.splitext(base)

    assert_ne(base, "")
    assert_eq(ext, ".qcow2")

    shutil.copy(base, "base.qcow2")


def directories_match(ctx, actual=None, expected=None):
    assert_eq = globals()["assert_eq"]

    efiles = list(sorted(os.listdir(expected)))
    afiles = list(sorted(os.listdir(actual)))
    assert_eq(efiles, afiles)
    for filename in efiles:
        with open(os.path.join(expected, filename)) as f:
            edata = yaml.safe_load(f)
        with open(os.path.join(actual, filename)) as f:
            adata = yaml.safe_load(f)
            if "runcmd" in adata:
                del adata["runcmd"]
        logging.debug(f"directories_match: filename={filename}")
        logging.debug(f"directories_match: edata={edata!r}")
        logging.debug(f"directories_match: adata={adata!r}")
        assert_eq(edata, adata)


def stdout_json_matches(ctx, filename=None):
    runcmd_get_stdout = globals()["runcmd_get_stdout"]

    stdout = io.StringIO(runcmd_get_stdout(ctx))
    actual = yaml.safe_load(stdout)

    with open(filename) as f:
        expected = json.load(f)

    expected = _expand_tilde(expected)

    logging.debug(f"actual: {actual}")
    logging.debug(f"expect: {expected}")
    _assert_equal_objects(actual, expected)


def _expand_tilde(o):
    if isinstance(o, str):
        return os.path.expanduser(o)
    elif isinstance(o, dict):
        return {key: _expand_tilde(o[key]) for key in o}
    elif isinstance(o, list):
        return [_expand_tilde(value) for value in o]
    else:
        return o


def _assert_equal_objects(a, b):
    logging.debug(f"comparing {a!r} and {b!r}")
    assert type(a) == type(b)
    if isinstance(a, dict):
        logging.debug("both a and b are dicts")
        for key in a:
            assert key in b, f"wanted b to have key {key!r}"
            logging.debug(f"comparing values for key {key!r}")
            _assert_equal_objects(a[key], b[key])
        for key in b:
            assert key in a, f"wanted a to have key {key!r}"
    elif isinstance(a, list):
        assert len(a) == len(
            b
        ), f"wanted a and b to be of same length ({len(a)} vs {len(b)})"
        for i in range(len(a)):
            _assert_equal_objects(a[i], b[i])
    else:
        assert a == b, f"wanted {a!r} and {b!r} to be equal"
