#!/usr/bin/env nix-shell
#! nix-shell -i python3 --pure
#! nix-shell -p python3 python3Packages.coloredlogs

import argparse
import logging
import os
import sys

import coloredlogs

coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
log = logging.getLogger()

# Coding conventions:
# No leading or trailing slashes. Let os.path.join do its job

# TODO Config arparse and pass args to the functions. No globals

# Finding directories
assert "HOME" in os.environ, "Home directory unknown"
DOCS = os.path.realpath(os.path.join(os.environ["HOME"], "Documents"))
assert os.path.isdir(DOCS), "Documents folder not found"
ARCS = os.path.realpath(os.path.join(os.environ["HOME"], "Archives"))
assert os.path.isdir(ARCS), "Archives folder not found"


def dirRange(relpath):
    splits = relpath.split(os.path.sep)
    res = list()

    for p in range(len(splits)):
        partPath = os.path.join(*splits[: p + 1])

        arcPath = os.path.join(os.path.join(ARCS, partPath))
        docPath = os.path.join(os.path.join(DOCS, partPath))

        res.append((docPath, arcPath))

    return res


def travel(relpath):
    """
    Dunno what this will do, let's write code and see.
    """
    wholeRange = dirRange(relpath)
    for tup in wholeRange:
        isLast = wholeRange[-1] == tup
        docPath, arcPath = tup
        linkPath = os.path.relpath(arcPath, start=docPath)

        log.debug(f"47 {tup}")

        if not os.path.exists(docPath) and not os.path.exists(arcPath):
            log.error("Not existing")
            sys.exit(1)
        elif os.path.isdir(docPath) and os.path.isdir(arcPath) and not isLast:
            log.debug("Both folder")
            continue
        elif os.path.isdir(docPath) and os.path.isdir(arcPath) and isLast:
            log.error("This should fail for some reason, maybe")
            sys.exit(1)
        elif os.path.islink(docPath) and os.path.exists(arcPath):
            currentLink = os.readlink(docPath)
            if currentLink != linkPath:
                log.warning(
                    f"'{docPath}' is pointing to '{currentLink}' "
                    + f"but should point to '{linkPath}'."
                )
                # TODO Fixing if asked for
                sys.exit(1)
            log.debug("Early link already exists {docPath} → {arcPath}")
            return
        elif not os.path.exists(docPath) and os.path.exists(arcPath):
            log.debug("Only existing on archive side, linking")
            print(f"ln -s {linkPath} {docPath}")
        elif os.path.exists(docPath) and not os.path.exists(arcPath) and isLast:
            log.debug("Only existing on doc side, moving and linking")
            print(f"mv {docPath} {arcPath}")
            print(f"ln -s {linkPath} {docPath}")
        elif os.path.exists(docPath) and not os.path.exists(arcPath) and not isLast:
            raise NotImplementedError("Here comes the trouble")
        else:
            log.error("Unhandled case")
            sys.exit(1)


def ensureLink(relpath):
    """
    Ensure that ~/Documents/$relpath points to ~/Archives/$relpath
    """
    arcPath = os.path.join(os.path.join(ARCS, relpath))
    docPath = os.path.join(os.path.join(DOCS, relpath))
    assert os.path.exists(arcPath)

    # For each tree element of the path
    for docPath, arcPath in dirRange(relpath):
        linkPath = os.path.relpath(arcPath, start=docPath)

        def installLink():
            if args.dry:
                print(f"ln -s {linkPath} {docPath}")
            else:
                os.symlink(linkPath, docPath)

        if os.path.islink(docPath):
            currentLink = os.readlink(docPath)
            if currentLink != linkPath:
                log.warning(
                    f"'{docPath}' is pointing to '{currentLink}' "
                    + f"but should point to '{linkPath}'. Fixing"
                )
                if args.dry:
                    print(f"rm {docPath}")
                else:
                    os.unlink(docPath)
                    installLink()
                    return
            elif not os.path.exists(docPath):
                installLink()
                return
            elif os.path.isdir(docPath):
                continue
            else:
                raise RuntimeError(
                    f"'{docPath}' exists and is not a directory "
                    + f"or a link. Unable to link it to '{linkPath}'"
                )
    raise RuntimeError(
        f"'{docPath}' is a directory. Unable to link it to " + f"'{linkPath}'"
    )


def archive(docdir):
    docdir = os.path.realpath(args.dir)
    assert os.path.isdir(docdir), docdir + " must be a directory"

    assert docdir.startswith(DOCS), "Directory is not in the document folder"
    assert not docdir.startswith(ARCS), "Directory is already in the archive folder"

    reldir = os.path.relpath(docdir, DOCS)
    print("ARC", reldir)

    arcdir = os.path.join(ARCS, reldir)
    parentArcdir = os.path.realpath(os.path.join(arcdir, ".."))
    parentDocdir = os.path.realpath(os.path.join(docdir, ".."))
    linkDest = os.path.relpath(arcdir, parentDocdir)

    # BULLSHIT

    # If the directory exists
    if os.path.isdir(arcdir):
        return
    # for f in os.listdir(arcdir):
    #     assert os.path.isdir(f), "Something unknown in Archive dir")
    #     archive(os.path.join(arcdir, f))

    # If the directory doesn't exist, create the directories under it and move all the folder
    else:
        if args.dry:
            print("mkdir -p", parentArcdir)
        else:
            os.makedirs(parentArcdir, exist_ok=True)

        if args.dry:
            print("mv", docdir, arcdir)
        else:
            os.rename(docdir, arcdir)

        if args.dry:
            print("ln -s", linkDest, docdir)
        else:
            os.symlink(linkDest, docdir)


def unarchive(arcdir):
    pass


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Place a folder in ~/Documents in ~/Documents/Archives and symlink it"
    )
    parser.add_argument(
        "dir", metavar="DIRECTORY", type=str, help="The directory to archive"
    )
    parser.add_argument("-d", "--dry", action="store_true")
    args = parser.parse_args()
    args.dry = True  # DEBUG

    # archive(args.dir)
    ensureLink(args.dir)