# -*- coding: utf-8 -*-
import errno
import os
from pathlib import PosixPath as Path
from typing import Any
from typing import Callable
from typing import Iterator

import attrs

try:
    from typing import Self
except ImportError:
    from typing_extensions import Self

__all__ = ['MountPoint', 'get_all_mount_points', 'get_mount_point', 'AttrFieldValidatorFactory']


@attrs.frozen(slots=True, kw_only=True)
class MountPoint:
    source: str
    target: Path
    fstype: str
    options: tuple[str, ...]
    freq: int
    passno: int

    def __fspath__(self) -> str:
        return os.fspath(self.target)

    @classmethod
    def parse(cls, line: str) -> Self:
        line_parts = [s.replace('\\040', ' ').replace('\\012', '\n') for s in line.strip(' \n').split(' ')]
        if len(line_parts) != 6:
            raise ValueError(f'Not a valid line of mount info: {line!r}')

        fs_spec = line_parts[0]
        fs_file = line_parts[1]
        fs_vfstype = line_parts[2]
        fs_mntops = tuple(line_parts[3].split(','))
        raw_fs_freq = line_parts[4]
        if not raw_fs_freq.isdigit():
            raise ValueError(f'Invalid value of fs_freq: {raw_fs_freq!r}')
        fs_freq = int(raw_fs_freq)
        raw_fs_passno = line_parts[5]
        if not raw_fs_passno.isdigit():
            raise ValueError(f'Invalid value of fs_passno: {raw_fs_passno!r}')
        fs_passno = int(raw_fs_passno)

        target = Path(str(bytes(fs_file, encoding='raw_unicode_escape'), encoding='unicode_escape')).resolve()

        return cls(source=fs_spec, target=target, fstype=fs_vfstype, options=fs_mntops, freq=fs_freq, passno=fs_passno)


def _iter_get_all_mounts_points() -> Iterator[MountPoint]:
    with open('/proc/mounts', mode='r', newline='') as f:
        mount_info_content = f.read()
    for line in mount_info_content.split('\n'):
        if line.strip():
            yield MountPoint.parse(line)


def get_all_mount_points() -> list[MountPoint]:
    return list(_iter_get_all_mounts_points())


def get_mount_point(fs_file: str | bytes | os.PathLike) -> MountPoint | None:
    fs_file_path = Path(os.fsdecode(fs_file)).resolve()
    for mount_point in _iter_get_all_mounts_points():
        if mount_point.target.resolve() == fs_file_path:
            return mount_point


class AttrFieldValidatorFactory:
    @staticmethod
    def executable_field(basename: str | tuple[str, ...]) -> Callable[[Any, attrs.Attribute, Any], None]:
        def validator(__: Any, ___: attrs.Attribute, value: Any) -> None:
            if not os.path.exists(value):
                raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), value)
            elif os.path.isdir(value):
                raise IsADirectoryError(errno.EISDIR, os.strerror(errno.EISDIR), value)
            elif not os.access(value, os.X_OK):
                raise PermissionError(errno.EPERM, os.strerror(errno.EPERM), value)

            actual_basename = os.path.basename(value).lower()
            if isinstance(basename, tuple):
                basenames: list[str] = [s.lower() for s in basename]
                if actual_basename not in basenames:
                    raise ValueError(f'Executable name must be one of the following: {basenames!r} (got {actual_basename!r})')
            elif actual_basename != basename.lower():
                raise ValueError(f'Executable name must be {basename!r} (got {actual_basename!r}')

        return validator
