import contextlib
import os
import sys
from typing import Union

from more_itertools import unique_everseen


if sys.version_info >= (3, 9):
    StrPath = Union[str, os.PathLike[str]]  #  Same as _typeshed.StrPath
else:
    StrPath = Union[str, os.PathLike]


def ensure_directory(path):
    """Ensure that the parent directory of `path` exists"""
    dirname = os.path.dirname(path)
    os.makedirs(dirname, exist_ok=True)


def same_path(p1: StrPath, p2: StrPath) -> bool:
    """Differs from os.path.samefile because it does not require paths to exist.
    Purely string based (no comparison between i-nodes).
    >>> same_path("a/b", "./a/b")
    True
    >>> same_path("a/b", "a/./b")
    True
    >>> same_path("a/b", "././a/b")
    True
    >>> same_path("a/b", "./a/b/c/..")
    True
    >>> same_path("a/b", "../a/b/c")
    False
    >>> same_path("a", "a/b")
    False
    """
    return normpath(p1) == normpath(p2)


def normpath(filename: StrPath) -> str:
    """Normalize a file/dir name for comparison purposes."""
    # See pkg_resources.normalize_path for notes about cygwin
    file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename
    return os.path.normcase(os.path.realpath(os.path.normpath(file)))


@contextlib.contextmanager
def paths_on_pythonpath(paths):
    """
    Add the indicated paths to the head of the PYTHONPATH environment
    variable so that subprocesses will also see the packages at
    these paths.

    Do this in a context that restores the value on exit.

    >>> getfixture('monkeypatch').setenv('PYTHONPATH', 'anything')
    >>> with paths_on_pythonpath(['foo', 'bar']):
    ...     assert 'foo' in os.environ['PYTHONPATH']
    ...     assert 'anything' in os.environ['PYTHONPATH']
    >>> os.environ['PYTHONPATH']
    'anything'

    >>> getfixture('monkeypatch').delenv('PYTHONPATH')
    >>> with paths_on_pythonpath(['foo', 'bar']):
    ...     assert 'foo' in os.environ['PYTHONPATH']
    >>> os.environ.get('PYTHONPATH')
    """
    nothing = object()
    orig_pythonpath = os.environ.get('PYTHONPATH', nothing)
    current_pythonpath = os.environ.get('PYTHONPATH', '')
    try:
        prefix = os.pathsep.join(unique_everseen(paths))
        to_join = filter(None, [prefix, current_pythonpath])
        new_path = os.pathsep.join(to_join)
        if new_path:
            os.environ['PYTHONPATH'] = new_path
        yield
    finally:
        if orig_pythonpath is nothing:
            os.environ.pop('PYTHONPATH', None)
        else:
            os.environ['PYTHONPATH'] = orig_pythonpath
