import os
import sys
from pathlib import Path
from typing import Sequence, Tuple, Callable

import pytest

from clarabot_nano.utility.path.resolve import resolve_path
from clarabot_nano.utility.path.validate import normalize_absolute_path
from clarabot_nano_tests.testing.file import temp_path


def _check_dir_missing_file(items: Sequence[Tuple[str, str]]):
    assert len(items) == 2
    for item, result in items:
        assert resolve_path(item) == Path(result), f"X\n{item}\n{result}\n{resolve_path(item)}\n{str(Path(result))}"
    assert resolve_path(items[0][0], True) == Path(items[0][1])
    with pytest.raises(FileNotFoundError):
        resolve_path(items[1][0], True)


def _check_dir_file_not_dir(items: Sequence[Tuple[str, str]]):
    for item, result in items:
        assert resolve_path(item) == Path(result), f"X\n{item}\n{result}\n{resolve_path(item)}\n{str(Path(result))}"
    assert resolve_path(items[0][0], True) == Path(items[0][1])
    assert resolve_path(items[1][0], True) == Path(items[1][1])
    with pytest.raises(FileNotFoundError if sys.platform.startswith("win") else NotADirectoryError):
        assert resolve_path(items[2][0], True) == Path(items[2][1])


def _universal_resolve_test(p: str, variant_gen_fn: Callable):
    # simple missing file
    px = os.path.join(p, "x")
    for items in variant_gen_fn([p, px]):
        _check_dir_missing_file(items)

    # simple path under file
    Path(px).write_text("x")
    pxy = os.path.join(px, "y")
    for items in variant_gen_fn([p, px, pxy]):
        _check_dir_file_not_dir(items)

    # looped symlink
    pa = Path(p, "a")
    pb = Path(p, "b")
    os.symlink(pa, pb)
    os.symlink(pb, pa)
    if sys.platform.startswith("win"):
        assert resolve_path(pa) == pa  # TODO: loop link does not raise on windows
    else:
        with pytest.raises(FileNotFoundError):
            resolve_path(pa)
    with pytest.raises(FileNotFoundError):
        resolve_path(pa, True)

    # broken symlink
    pc = Path(p, "c")
    pd = Path(p, "d")
    pe = Path(p, "e")
    os.symlink(pe, pc)
    os.symlink(pe, pd, target_is_directory=True)
    assert resolve_path(pc) == pe
    assert resolve_path(pd) == pe
    with pytest.raises(FileNotFoundError):
        resolve_path(pc, True)
    with pytest.raises(FileNotFoundError):
        resolve_path(pd, True)

    # resolve relative symlinks
    prd = resolve_path(Path(p, "rd"))
    prd.mkdir()
    prdl0 = Path(p, "rd", "l0")
    # correct type (dir)
    os.symlink("..", prdl0, target_is_directory=True)
    assert resolve_path(prdl0) == Path(p)
    assert resolve_path(prdl0, True) == Path(p)
    # FIXME: it seems something tries to detect the link type, but does not always succeed (win)
    # invalid type (file to dir)
    # pdl1 = Path(p, "d", "l1")
    # os.symlink("..", pdl1, target_is_directory=False)
    # # pdl1.symlink_to("..")
    # with pytest.raises(PermissionError):
    #     assert resolve_path(pdl1) == Path(f"\\\\?\\{p}")
    # with pytest.raises(PermissionError):
    #     resolve_path(pdl1, True)


@pytest.mark.skipif(sys.platform.startswith("win"), reason="for posix only")
def test_utility_path_resolve_path_posix(tmpdir):
    p = normalize_absolute_path(temp_path(tmpdir))
    os.makedirs(p)

    def _create_path_variants(ps):
        return [[(x, x) for x in ps]]

    _universal_resolve_test(p, _create_path_variants)


@pytest.mark.skipif(not sys.platform.startswith("win"), reason="for windows only")
def test_utility_path_resolve_path_nt(tmpdir):
    p = str(Path(normalize_absolute_path(temp_path(tmpdir))))
    os.makedirs(p)
    assert p.find("/") == -1

    def _create_path_variants(ps):
        variants = [
            ps,
            [f"\\\\LOCALHOST\\C${x[6:]}" for x in ps],
            [f"\\\\?\\UNC\\LOCALHOST\\C${x[6:]}" for x in ps],
            [f"\\\\?\\{x[4:]}" for x in ps],
        ]
        for variant in list(variants):
            variants.append([v.replace("\\", "/") for v in variant])
        for vs in variants:
            for vi, v in enumerate(list(vs)):
                sep = "\\" if v.find("/") == -1 else "/"
                if not v.startswith(sep):
                    vs[vi] = (v, f"{sep}{sep}?{sep}{v}")
                elif not v.startswith(f"{sep}{sep}?{sep}"):
                    vs[vi] = (v, f"{sep}{sep}?{sep}UNC{v[1:]}")
                else:
                    vs[vi] = (v, v)
        return variants

    _universal_resolve_test(p, _create_path_variants)

    # path prefix drive-case normalization consistency
    tmp = "\\\\?\\c:\\windows"
    assert str(Path(tmp)) == tmp
    assert str(resolve_path(tmp)) == "\\\\?\\C:\\Windows"
