import re
import sys
from os import PathLike
from pathlib import Path, PurePosixPath, PureWindowsPath
from typing import Union, Generator, Sequence

from clarabot_nano.utility.path.resolve import resolve_path
from clarabot_nano.utility.path.validate import ValidationError, validate_name, validate_path, normalize_absolute_path

RELATIVE_DIRECTIVES_RE = re.compile(r"^\.{1,2}$")


class ConditionalValidationError(ValidationError):
    """
    This is used instead its base class where validity is up to policy or a filesystem change may make the
    path resolution valid.
    """


def get_path_flavour():
    return PureWindowsPath._flavour if sys.platform.startswith("win") else PurePosixPath._flavour


def ensure_path(path: Union[str, Path]) -> Path:
    """
    Coerce input types to the platform *Path object.
    """
    if isinstance(path, Path):
        return path
    if isinstance(path, str) and path:
        return Path(path)
    raise ValidationError("Invalid path")


def normalize_validate_absolute_path(path: Union[PathLike, str]) -> str:
    """
    Normalizing and validating an absolute path.
    """
    path = normalize_absolute_path(path)
    validate_path(path)
    return path


def normalize_validate_resolve_absolute_path(path: Union[PathLike, str]) -> Path:
    """
    Resolve an absolute Path. This is always strict. (path must exist) If control over symlink resolution is
    needed then split the root drive and resolve from that with validate_resolve_path_generator().
    """
    return resolve_path(normalize_validate_absolute_path(path), strict=True)


def validate_resolve_path_generator(
    root_parts: Sequence[str], parts: Sequence[str], allow_symlinks: bool = False
) -> Generator[Path, None, None]:
    """
    The root parts must be absolute and already normalized, validated and resolved like:
        normalize_validate_resolve_absolute_path(path).parts
    This is made to be a generator so it can be re-used with custom validation after each step.
    NOTE: Chained symlinks are not checked and validated step by step:
        - link A -> link B -> file  # file is considered link A (link B is transparent)
    """
    if not parts:
        raise ValidationError("Path must not be empty")
    i = 1
    while True:
        if i > 1 or root_parts:  # first is always root when path is absolute
            validate_name(parts[i - 1], dos=False)  # Path is normalized, so no DOS reserved names exist.
        p = Path(*root_parts, *parts[:i])
        if len(root_parts) + i <= 2:
            # Check full path for root and the first item. For NTFS there are reserved names that are only in the
            # filesystem root. To check those we need the root with the first item.
            validate_path(p)
        if not allow_symlinks and p.is_symlink():
            raise ConditionalValidationError("Path may not have symlink")
        p = resolve_path(p, strict=True)  # resolve always return absolute path (and not a symlink)
        yield p
        if i >= len(parts):
            validate_path(p)  # final validation
            break
        i += 1


def posix_hierarchy_iter(path: str):
    """
    Path hierarchy iterator with posix separators. The path must be normalized before calling this function.
    """
    if not path:
        return
    if path.startswith("/"):
        if path.startswith("//"):  # nt - UNC path
            yield "//"
            i = 1
        else:
            yield "/"
            i = 0
        if len(path) == i + 1:
            return
    else:
        i = 0
    while True:
        i = path.find("/", i + 1)
        if i == -1:
            if not path.endswith("/"):
                yield path
            break
        else:
            yield path[:i]
