from datetime import datetime

import logging
import os
import re
import shutil
import time


def files_relative(filename):
    return "./" + filename


def _files_create_from_embedded(
    ctx, filename_on_disk=None, embedded_file=None, executable=False
):
    get_file = globals()["get_file"]

    files_make_directory(ctx, path=os.path.dirname(filename_on_disk) or ".")
    filename_on_disk = files_relative(filename_on_disk)
    with open(filename_on_disk, "wb") as f:
        f.write(get_file(embedded_file))
    if executable:
        os.chmod(filename_on_disk, 0o755)


def files_create_from_embedded(ctx, embedded_file=None):
    _files_create_from_embedded(
        ctx,
        filename_on_disk=embedded_file,
        embedded_file=embedded_file,
        executable=False,
    )


def files_create_from_embedded_with_other_name(
    ctx, filename_on_disk=None, embedded_file=None
):
    _files_create_from_embedded(
        ctx,
        filename_on_disk=filename_on_disk,
        embedded_file=embedded_file,
        executable=False,
    )


def files_create_executable_from_embedded(ctx, embedded_file=None):
    _files_create_from_embedded(
        ctx,
        filename_on_disk=embedded_file,
        embedded_file=embedded_file,
        executable=True,
    )


def files_create_executable_from_embedded_with_other_name(
    ctx, filename_on_disk=None, embedded_file=None
):
    _files_create_from_embedded(
        ctx,
        filename_on_disk=filename_on_disk,
        embedded_file=embedded_file,
        executable=True,
    )


def files_create_from_text(ctx, filename=None, text=None):
    files_make_directory(ctx, path=os.path.dirname(filename) or ".")
    filename = files_relative(filename)
    with open(filename, "w") as f:
        f.write(text)


def files_remove_file(ctx, filename=None):
    filename = files_relative(filename)
    os.remove(filename)


def files_make_directory(ctx, path=None):
    path = files_relative(path)
    if not os.path.exists(path):
        os.makedirs(path)


def files_remove_directory(ctx, path=None):
    path = files_relative(path)
    shutil.rmtree(path)


def files_remove_empty_directory(ctx, path=None):
    path = files_relative(path)
    os.rmdir(path)


def files_file_exists(ctx, filename=None):
    assert_eq = globals()["assert_eq"]
    filename = files_relative(filename)
    assert_eq(os.path.exists(filename), True)


def files_file_does_not_exist(ctx, filename=None):
    assert_eq = globals()["assert_eq"]
    filename = files_relative(filename)
    assert_eq(os.path.exists(filename), False)


def files_directory_exists(ctx, path=None):
    assert_eq = globals()["assert_eq"]
    path = files_relative(path)
    assert_eq(os.path.isdir(path), True)


def files_directory_does_not_exist(ctx, path=None):
    assert_eq = globals()["assert_eq"]
    path = files_relative(path)
    assert_eq(os.path.isdir(path), False)


def files_directory_is_empty(ctx, path=None):
    assert_eq = globals()["assert_eq"]
    path = files_relative(path)
    assert_eq(os.listdir(path), [])


def files_directory_is_not_empty(ctx, path=None):
    assert_ne = globals()["assert_ne"]
    path = files_relative(path)
    assert_ne(os.listdir(path), False)


def files_only_these_exist(ctx, filenames=None):
    assert_eq = globals()["assert_eq"]
    filenames = filenames.replace(",", "").split()
    assert_eq(set(os.listdir(".")), set(filenames))


def files_file_contains(ctx, filename=None, data=None):
    assert_eq = globals()["assert_eq"]
    filename = files_relative(filename)
    with open(filename, "rb") as f:
        actual = f.read()
        actual = actual.decode("UTF-8")
    assert_eq(data in actual, True)


def files_file_doesnt_contain(ctx, filename=None, data=None):
    assert_eq = globals()["assert_eq"]
    filename = files_relative(filename)
    with open(filename, "rb") as f:
        actual = f.read()
        actual = actual.decode("UTF-8")
    assert_eq(data in actual, False)


def files_file_matches_regex(ctx, filename=None, regex=None):
    assert_eq = globals()["assert_eq"]
    filename = files_relative(filename)
    with open(filename) as f:
        content = f.read()
    m = re.search(regex, content)
    if m is None:
        logging.debug("files_file_matches_regex: no match")
        logging.debug(f"  filenamed: {filename}")
        logging.debug(f"  regex: {regex}")
        logging.debug(f"  content: {content}")
        logging.debug(f"  match: {m}")
    assert_eq(bool(m), True)


def files_match(ctx, filename1=None, filename2=None):
    assert_eq = globals()["assert_eq"]
    filename1 = files_relative(filename1)
    filename2 = files_relative(filename2)
    with open(filename1, "rb") as f:
        data1 = f.read()
    with open(filename2, "rb") as f:
        data2 = f.read()
    assert_eq(data1, data2)


def files_do_not_match(ctx, filename1=None, filename2=None):
    assert_ne = globals()["assert_ne"]
    filename1 = files_relative(filename1)
    filename2 = files_relative(filename2)
    with open(filename1, "rb") as f:
        data1 = f.read()
    with open(filename2, "rb") as f:
        data2 = f.read()
    assert_ne(data1, data2)


def file_and_embedded_file_match(ctx, filename=None, embedded=None):
    assert_eq = globals()["assert_eq"]
    get_file = globals()["get_file"]
    filename = files_relative(filename)
    with open(filename, "rb") as f:
        data1 = f.read()
    data2 = get_file(embedded)
    assert_eq(data1, data2)


def file_and_embedded_file_do_not_match(ctx, filename=None, embedded=None):
    assert_ne = globals()["assert_ne"]
    get_file = globals()["get_file"]
    filename = files_relative(filename)
    with open(filename, "rb") as f:
        data1 = f.read()
    data2 = get_file(embedded)
    assert_ne(data1, data2)


def files_touch_with_timestamp(ctx, filename=None, mtime=None):
    t = datetime.strptime(mtime + " +0000", "%Y-%m-%d %H:%M:%S %z")
    ts = t.timestamp()
    _files_touch(filename, ts)


def files_touch(ctx, filename=None):
    _files_touch(filename, None)


def _files_touch(filename, ts):
    filename = files_relative(filename)
    if not os.path.exists(filename):
        open(filename, "w").close()
    times = None
    if ts is not None:
        times = (ts, ts)
    os.utime(filename, times=times)


def files_mtime_is_recent(ctx, filename=None):
    filename = files_relative(filename)
    st = os.stat(filename)
    age = abs(st.st_mtime - time.time())
    assert age < 1.0


def files_mtime_is_ancient(ctx, filename=None):
    filename = files_relative(filename)
    st = os.stat(filename)
    age = abs(st.st_mtime - time.time())
    year = 365 * 24 * 60 * 60
    required = 39 * year
    logging.debug(f"ancient? mtime={st.st_mtime} age={age} required={required}")
    assert age > required


def files_remember_metadata(ctx, filename=None):
    log_value = globals()["log_value"]

    meta = _files_remembered(ctx)
    meta[filename] = _files_get_metadata(filename)
    logging.debug("files_remember_metadata:")
    log_value("meta", 1, meta)
    log_value("ctx", 1, ctx.as_dict())


# Check that current metadata of a file is as stored in the context.
def files_has_remembered_metadata(ctx, filename=None):
    assert_dict_eq = globals()["assert_dict_eq"]
    log_value = globals()["log_value"]

    meta = _files_remembered(ctx)
    logging.debug("files_has_remembered_metadata:")
    log_value("meta", 1, meta)
    log_value("ctx", 1, ctx.as_dict())

    assert_dict_eq(meta[filename], _files_get_metadata(filename))


def files_has_different_metadata(ctx, filename=None):
    assert_ne = globals()["assert_ne"]
    meta = _files_remembered(ctx)
    assert_ne(meta[filename], _files_get_metadata(filename))


def _files_remembered(ctx):
    ns = ctx.declare("_files")
    return ns.get("remembered-metadata", {})


def _files_get_metadata(filename):
    filename = files_relative(filename)
    st = os.lstat(filename)
    keys = ["st_dev", "st_gid", "st_ino", "st_mode", "st_mtime", "st_size", "st_uid"]
    return {key: getattr(st, key) for key in keys}
