import sys
from collections import OrderedDict
from collections.abc import Iterable
from datetime import datetime
from pathlib import Path
from re import Pattern
from typing import Any, Callable, Literal, Protocol

from _typeshed import StrPath
from typing_extensions import Self

from . import __version__
from ._errors import TorfError
from ._magnet import Magnet
from ._utils import File, Filepath, Filepaths, Files, MonitoredList, Trackers, URLs

if sys.version_info < (3, 11):
    from typing_extensions import NotRequired, Required, TypedDict
else:
    from typing import NotRequired, Required, TypedDict

class _WritableBinaryStream(Protocol):
    def seek(self, offset: int, whence: int = 0) -> int: ...
    def seekable(self) -> bool: ...
    def truncate(self, size: int | None = None) -> int: ...
    def write(self, s: bytes) -> int: ...

class _ReadableBinaryStream(Protocol):
    def read(self, n: int = -1) -> bytes: ...

_PACKAGE_NAME: str = ...
NCORES: int = ...
DEFAULT_TORRENT_NAME: Literal["UNNAMED TORRENT"] = ...


class _FilesDict(TypedDict):
    length: int
    path: list[str]

_InfoDict = TypedDict(
    "_InfoDict",
    {
        "name": str,
        "piece length": int,
        "pieces": bytes,
        "length": NotRequired[int],
        "files": NotRequired[list[_FilesDict]],
        "private": NotRequired[bool],
        "source": NotRequired[str]
    }
)
"""See BEP 0003: https://www.bittorrent.org/beps/bep_0003.html"""

_MetaInfo = TypedDict(
    "_MetaInfo",
    {
        "info": Required[_InfoDict],
        "announce": str,
        "announce-list": list[list[str]],
        "comment": str,
        "created by": str,
        "creation date": datetime,
        "url-list": list[str],
    },
    total=False
)

class Torrent:
    def __init__(
        self,
        path: StrPath | None = None,
        name: str | None = None,
        exclude_globs: Iterable[str] = (),
        exclude_regexs: Iterable[str] = (),
        include_globs: Iterable[str] = (),
        include_regexs: Iterable[str] = (),
        trackers: Iterable[str] | str | None = None,
        webseeds: Iterable[str] | str | None = None,
        httpseeds: Iterable[str] | str | None = None,
        private: bool | None = None,
        comment: str | None = None,
        source: str | None = None,
        creation_date: int | float | datetime | None = None,
        created_by: str | None = f"{_PACKAGE_NAME} {__version__}",
        piece_size: int | None = None,
        piece_size_min: int | None = None,
        piece_size_max: int | None = None,
        randomize_infohash: bool = False,
    ) -> None: ...
    @property
    def metainfo(self) -> _MetaInfo: ...
    @property
    def path(self) -> Path | None: ...
    @path.setter
    def path(self, value: StrPath | None) -> None: ...
    @property
    def location(self) -> Path | None: ...
    @property
    def files(self) -> Files: ...
    @files.setter
    def files(self, files: Iterable[File]) -> None: ...
    @property
    def filepaths(self) -> Filepaths: ...
    @filepaths.setter
    def filepaths(self, filepaths: Iterable[Filepath]) -> None: ...
    @property
    def exclude_globs(self) -> MonitoredList[str]: ...
    @exclude_globs.setter
    def exclude_globs(self, value: Iterable[str]) -> None: ...
    @property
    def include_globs(self) -> MonitoredList[str]: ...
    @include_globs.setter
    def include_globs(self, value: Iterable[str]) -> None: ...
    @property
    def exclude_regexs(self) -> MonitoredList[Pattern[str]]: ...
    @exclude_regexs.setter
    def exclude_regexs(self, value: Iterable[str]) -> None: ...
    @property
    def include_regexs(self) -> MonitoredList[Pattern[str]]: ...
    @include_regexs.setter
    def include_regexs(self, value: Iterable[str]) -> None: ...
    @property
    def filetree(self) -> dict[str, dict[str, File]]: ...
    @property
    def name(self) -> str | None: ...
    @name.setter
    def name(self, value: str | None) -> None: ...
    @property
    def mode(self) -> Literal["singlefile", "multifile"] | None: ...
    @property
    def size(self) -> int: ...
    def partial_size(self, path: StrPath | Iterable[StrPath]) -> int: ...
    @property
    def piece_size(self) -> int: ...
    @piece_size.setter
    def piece_size(self, value: int | None) -> None: ...
    @property
    def piece_size_min(self) -> int: ...
    @piece_size_min.setter
    def piece_size_min(self, piece_size_min: int | None) -> None: ...
    @property
    def piece_size_max(self) -> int: ...
    @piece_size_max.setter
    def piece_size_max(self, piece_size_max: int | None) -> None: ...

    piece_size_min_default: int = ...
    piece_size_max_default: int = ...

    @classmethod
    def calculate_piece_size(cls, size: int, min_size: int | None = None, max_size: int | None = None) -> int: ...
    @property
    def pieces(self) -> int: ...
    @property
    def hashes(self) -> tuple[bytes, ...]: ...
    @property
    def trackers(self) -> Trackers: ...
    @trackers.setter
    def trackers(self, value: str | Iterable[str] | None) -> None: ...
    @property
    def webseeds(self) -> URLs: ...
    @webseeds.setter
    def webseeds(self, value: str | Iterable[str] | None) -> None: ...
    @property
    def httpseeds(self) -> URLs: ...
    @httpseeds.setter
    def httpseeds(self, value: str | Iterable[str] | None) -> None: ...
    @property
    def private(self) -> bool | None: ...
    @private.setter
    def private(self, value: bool | None) -> None: ...
    @property
    def comment(self) -> str | None: ...
    @comment.setter
    def comment(self, value: str | None) -> None: ...
    @property
    def creation_date(self) -> datetime | None: ...
    @creation_date.setter
    def creation_date(self, value: int | float | datetime | None) -> None: ...
    @property
    def created_by(self) -> str | None: ...
    @created_by.setter
    def created_by(self, value: str | None) -> None: ...
    @property
    def source(self) -> str | None: ...
    @source.setter
    def source(self, value: str | None) -> None: ...
    @property
    def infohash(self) -> str: ...
    @property
    def infohash_base32(self) -> bytes: ...
    @property
    def randomize_infohash(self) -> bool: ...
    @randomize_infohash.setter
    def randomize_infohash(self, value: bool) -> None: ...
    @property
    def is_ready(self) -> bool: ...
    def generate(
        self,
        threads: int | None = None,
        callback: Callable[[Torrent, str, int, int], Any] | None = None,
        interval: float = 0,
    ) -> bool: ...
    def verify(
        self,
        path: StrPath,
        threads: int | None = None,
        callback: Callable[[Torrent, str, int, int, int, bytes | None, TorfError | None], Any] | None = None,
        interval: float = 0,
    ) -> bool: ...
    def verify_filesize(
        self, path: StrPath, callback: Callable[[Torrent, str, str, int, int, TorfError | None], Any] | None = None
    ) -> bool: ...
    def validate(self) -> None: ...
    def convert(self) -> OrderedDict[bytes, Any]: ...
    def dump(self, validate: bool = True) -> bytes: ...
    def write_stream(self, stream: _WritableBinaryStream, validate: bool = True) -> None: ...
    def write(self, filepath: StrPath, validate: bool = True, overwrite: bool = False) -> None: ...
    def magnet(self, name: bool = True, size: bool = True, trackers: bool = True, tracker: bool = False) -> Magnet: ...

    MAX_TORRENT_FILE_SIZE: int = ...

    @classmethod
    def read_stream(cls, stream: bytes | bytearray | _ReadableBinaryStream, validate: bool = True) -> Self: ...
    @classmethod
    def read(cls, filepath: StrPath, validate: bool = True) -> Self: ...
    def copy(self) -> Self: ...
    def reuse(
        self,
        path: StrPath,
        callback: Callable[[Torrent, str | None, int, int, bool | None, TorfError | None], Any] | None = None,
        interval: float = 0,
    ) -> bool: ...
    def __repr__(self) -> str: ...
    def __eq__(self, other: object) -> bool: ...
