"""
b64t - High-performance base64 encoding/decoding

This package requires the system b64t package to be installed:
    macOS:  brew install b64t
    Linux:  apt install b64t
"""

import sys
from pathlib import Path


def _is_apple_python():
    """Detect if running on ANY Apple-provided Python.

    Checks base_prefix to detect the original interpreter even inside a venv.
    Two Apple Python locations:
      /System/Library/Frameworks/...       - macOS system Python (SIP-protected)
      /Library/Developer/CommandLineTools/  - Xcode CLT Python

    Safe Pythons (return False):
      /opt/homebrew/...                    - Homebrew ARM64
      /usr/local/...                       - Homebrew Intel
      /Library/Frameworks/Python.framework - python.org (note: no /System/)
      /Users/...                           - Conda/pyenv
    """
    prefix = getattr(sys, "base_prefix", sys.prefix)
    return (
        "/System/Library" in prefix or
        "/Library/Developer/CommandLineTools" in prefix
    )


def _is_rhel_based():
    """Detect if running on a RHEL-based system (RHEL, Fedora, CentOS, Rocky, AlmaLinux)."""
    if Path("/etc/redhat-release").exists():
        return True
    os_release = Path("/etc/os-release")
    if os_release.exists():
        try:
            content = os_release.read_text()
            for line in content.splitlines():
                if line.startswith("ID_LIKE="):
                    val = line.split("=", 1)[1].strip('"\'')
                    if "rhel" in val or "fedora" in val:
                        return True
                if line.startswith("ID="):
                    val = line.split("=", 1)[1].strip('"\'')
                    if val in ("rhel", "fedora", "centos", "rocky", "almalinux"):
                        return True
        except OSError:
            pass
    return False


def _is_freebsd():
    """Detect if running on FreeBSD."""
    return sys.platform.startswith("freebsd")


def _get_install_command():
    """Get the appropriate install command for the current platform."""
    if sys.platform == "darwin":
        return "brew install b64t"
    if _is_rhel_based():
        return "dnf install b64t"
    if _is_freebsd():
        return "pkg install b64t"
    return "apt install b64t"


# Detect Apple Python and choose loading strategy
_use_ctypes = _is_apple_python()

if _use_ctypes:
    # =========================================================================
    # Apple Python - use ctypes to bypass C extension ABI shim issues
    # =========================================================================
    import ctypes

    DYLIB_PATHS = [
        "/opt/homebrew/lib/libb64t.dylib",      # macOS ARM64
        "/usr/local/lib/libb64t.dylib",          # macOS Intel
        "C:\\Program Files\\b64t\\libb64t.dll",  # Windows Winget
    ]

    def _find_dylib():
        for path in DYLIB_PATHS:
            if Path(path).exists():
                return path
        return None

    _dylib_path = _find_dylib()
    if _dylib_path is None:
        raise ImportError(
            "b64t system library not found.\n"
            "\n"
            "This pip package requires the system b64t package:\n"
            "    brew install b64t\n"
            "\n"
            "Install the system package first, then use pip in a virtual environment."
        )

    _lib = ctypes.CDLL(_dylib_path)

    _lib.b64t_encode.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t]
    _lib.b64t_encode.restype = ctypes.c_size_t
    _lib.b64t_decode.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t]
    _lib.b64t_decode.restype = ctypes.c_size_t
    _lib.b64t_encodeSize.argtypes = [ctypes.c_size_t]
    _lib.b64t_encodeSize.restype = ctypes.c_size_t
    _lib.b64t_decodeSize.argtypes = [ctypes.c_size_t]
    _lib.b64t_decodeSize.restype = ctypes.c_size_t

    class B64TError(Exception):
        """Base exception for b64t errors."""
        pass

    class EncodingError(B64TError):
        """Error during base64 encoding."""
        pass

    class DecodingError(B64TError):
        """Error during base64 decoding."""
        pass

    __version__ = "0.1.26"

    def encode(data):
        """Encode bytes to base64.

        Args:
            data: bytes or bytearray

        Returns:
            bytes: Base64-encoded bytes
        """
        if not isinstance(data, (bytes, bytearray)):
            raise TypeError("data must be bytes or bytearray")
        if len(data) == 0:
            return b''
        output_size = _lib.b64t_encodeSize(len(data))
        output_buf = ctypes.create_string_buffer(output_size)
        encoded_size = _lib.b64t_encode(output_buf, bytes(data), len(data))
        if encoded_size == 0 and len(data) > 0:
            raise EncodingError("Encoding failed")
        return output_buf.raw[:encoded_size]

    def decode(data):
        """Decode base64 bytes to original bytes.

        Args:
            data: bytes or bytearray containing base64 data

        Returns:
            bytes: Decoded bytes
        """
        if not isinstance(data, (bytes, bytearray)):
            raise TypeError("data must be bytes or bytearray")
        if len(data) == 0:
            return b''
        if len(data) % 4 != 0:
            raise DecodingError("Invalid base64 input length (must be multiple of 4)")
        output_size = _lib.b64t_decodeSize(len(data))
        output_buf = ctypes.create_string_buffer(output_size)
        decoded_size = _lib.b64t_decode(output_buf, bytes(data), len(data))
        if decoded_size == 0 and len(data) > 0:
            raise DecodingError("Invalid base64 data")
        return output_buf.raw[:decoded_size]

    def encodePipe(source, chunk_size=65536):
        """Stream base64 encode from iterable or IO object.

        Args:
            source: Iterable yielding individual bytes (0-255) or IO object with read()
            chunk_size: Size in bytes to read from IO objects (default 65536)

        Yields:
            bytes: Base64-encoded bytes chunks
        """
        # Read in multiples of 3 to avoid intermediate base64 padding.
        # Base64 encodes 3 bytes -> 4 chars. Only the LAST chunk should have padding.
        read_size = (chunk_size // 3) * 3
        if read_size == 0:
            read_size = 3

        if hasattr(source, 'read'):
            while True:
                chunk = source.read(read_size)
                if not chunk:
                    break
                yield encode(chunk)
        else:
            it = iter(source)
            buffer = bytearray()
            buf_size = chunk_size if chunk_size > 0 else 65536
            for item in it:
                if not isinstance(item, int):
                    raise TypeError("Iterator must yield integers (0-255)")
                if item < 0 or item > 255:
                    raise ValueError(
                        "Byte value {} out of range (0-255)".format(item)
                    )
                buffer.append(item)
                if len(buffer) >= buf_size:
                    yield encode(bytes(buffer))
                    buffer = bytearray()
            if buffer:
                yield encode(bytes(buffer))

    def decodePipe(source, chunk_size=65536):
        """Stream base64 decode from iterable or IO object.

        Args:
            source: Iterable yielding individual bytes (0-255) or IO object with read()
            chunk_size: Size in bytes to read from IO objects (default 65536)

        Yields:
            bytes: Decoded bytes chunks
        """
        # Read in multiples of 4 (base64 input is always groups of 4)
        read_size = (chunk_size // 4) * 4
        if read_size == 0:
            read_size = 4

        if hasattr(source, 'read'):
            while True:
                chunk = source.read(read_size)
                if not chunk:
                    break
                yield decode(chunk)
        else:
            it = iter(source)
            buffer = bytearray()
            buf_size = chunk_size if chunk_size > 0 else 65536
            for item in it:
                if not isinstance(item, int):
                    raise TypeError("Iterator must yield integers (0-255)")
                if item < 0 or item > 255:
                    raise ValueError(
                        "Byte value {} out of range (0-255)".format(item)
                    )
                buffer.append(item)
                if len(buffer) >= buf_size:
                    yield decode(bytes(buffer))
                    buffer = bytearray()
            if buffer and len(buffer) % 4 == 0:
                yield decode(bytes(buffer))

else:
    # =========================================================================
    # Standard Python - load C extension (optimal performance)
    # =========================================================================

    SYSTEM_EXTENSION_PATHS = [
        # Linux (APT / Debian-based)
        "/usr/lib/python3/dist-packages/b64t.abi3.so",
        "/usr/local/lib/python3/dist-packages/b64t.abi3.so",
        # Linux (DNF / RHEL-based)
        "/usr/lib64/python3/site-packages/b64t.abi3.so",
        # macOS Homebrew ARM64 (Apple Silicon)
        "/opt/homebrew/lib/python3/dist-packages/b64t.abi3.so",
        # FreeBSD pkg
        "/usr/local/share/b64t/python/b64t.abi3.so",
        # Windows Scoop
        str(Path.home() / "scoop/apps/b64t/current/python/b64t.abi3.so"),
        # Windows Winget (System)
        "C:\\Program Files\\b64t\\python\\b64t.abi3.so",
        # Windows Winget (User)
        str(Path.home() / "AppData/Local/b64t/python/b64t.abi3.so"),
    ]

    def _find_system_extension():
        for path in SYSTEM_EXTENSION_PATHS:
            if Path(path).exists():
                return path
        return None

    _system_ext = _find_system_extension()
    if _system_ext is None:
        _install_cmd = _get_install_command()
        raise ImportError(
            "b64t system extension not found.\n"
            "\n"
            "This pip package requires the system b64t package:\n"
            "    {}\n"
            "\n"
            "Install the system package first, then use pip in a virtual environment.".format(
                _install_cmd
            )
        )

    # Import from the system extension (surgical load by exact path, no sys.path modification)
    try:
        import importlib.util
        spec = importlib.util.spec_from_file_location("_b64t_ext", _system_ext)
        _b64t = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(_b64t)

        # Re-export all public symbols
        encode = _b64t.encode
        decode = _b64t.decode
        encodePipe = _b64t.encodePipe
        decodePipe = _b64t.decodePipe
        B64TError = _b64t.B64TError
        EncodingError = _b64t.EncodingError
        DecodingError = _b64t.DecodingError
        __version__ = getattr(_b64t, "__version__", "0.1.0")

    except Exception as e:
        _reinstall_cmd = _get_install_command()
        if sys.platform != "darwin":
            _reinstall_cmd += " --reinstall"
        raise ImportError(
            "Failed to import system b64t extension from {}\n"
            "Error: {}\n"
            "\n"
            "Verify the system package is installed correctly:\n"
            "    {}\n".format(_system_ext, e, _reinstall_cmd)
        ) from e
