"""
Typing stuff
"""

from collections.abc import AsyncIterator, Hashable
from dataclasses import dataclass, fields
from datetime import datetime
from enum import IntEnum
from pathlib import Path
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Generic,
    Literal,
    NamedTuple,
    TypedDict,
    TypeVar,
    Union,
)

from slixmpp import Message, Presence
from slixmpp.types import PresenceShows, PresenceTypes, ResourceDict  # noqa: F401

if TYPE_CHECKING:
    from ..contact import LegacyContact
    from ..core.session import BaseSession
    from ..group import LegacyMUC
    from ..group.participant import LegacyParticipant

    AnyBaseSession = BaseSession[Any, Any]
else:
    # Hack to work around circular import.
    AnyBaseSession = int


LegacyGroupIdType = TypeVar("LegacyGroupIdType", bound=Hashable)
"""
Type of the unique identifier for groups, usually a str or an int,
but anything hashable should work.
"""
LegacyMessageType = TypeVar("LegacyMessageType", bound=Hashable)
LegacyThreadType = TypeVar("LegacyThreadType", bound=Hashable)
LegacyUserIdType = TypeVar("LegacyUserIdType", bound=Hashable)

LegacyContactType = TypeVar("LegacyContactType", bound="LegacyContact[Any]")
LegacyMUCType = TypeVar("LegacyMUCType", bound="LegacyMUC[Any, Any, Any, Any]")
LegacyParticipantType = TypeVar("LegacyParticipantType", bound="LegacyParticipant")

SessionType = TypeVar("SessionType", bound="BaseSession[Any, Any]")
Recipient = Union["LegacyMUC[Any, Any, Any, Any]", "LegacyContact[Any]"]
RecipientType = TypeVar("RecipientType", bound=Recipient)
Sender = Union["LegacyContact[Any]", "LegacyParticipant"]
LegacyFileIdType = int | str

ChatState = Literal["active", "composing", "gone", "inactive", "paused"]
ProcessingHint = Literal["no-store", "markable", "store"]
Marker = Literal["acknowledged", "received", "displayed"]
FieldType = Literal[
    "boolean",
    "fixed",
    "text-single",
    "text-multi",
    "jid-single",
    "jid-multi",
    "list-single",
    "list-multi",
    "text-private",
]
MucAffiliation = Literal["owner", "admin", "member", "outcast", "none"]
MucRole = Literal["visitor", "participant", "moderator", "none"]
# https://xmpp.org/registrar/disco-categories.html#client
ClientType = Literal[
    "bot", "console", "game", "handheld", "pc", "phone", "sms", "tablet", "web"
]
AttachmentDisposition = Literal["attachment", "inline"]


@dataclass
class MessageReference(Generic[LegacyMessageType]):
    """
    A "message reply", ie a "quoted message" (:xep:`0461`)

    At the very minimum, the legacy message ID attribute must be set, but to
    ensure that the quote is displayed in all XMPP clients, the author must also
    be set (use the string "user" if the slidge user is the author of the referenced
    message).
    The body is used as a fallback for XMPP clients that do not support :xep:`0461`
    of that failed to find the referenced message.
    """

    legacy_id: LegacyMessageType
    author: Union[Literal["user"], "LegacyParticipant", "LegacyContact"] | None = None
    body: str | None = None


@dataclass
class LegacyAttachment:
    """
    A file attachment to a message

    At the minimum, one of the ``path``, ``steam``, ``data`` or ``url`` attribute
    has to be set

    To be used with :meth:`.LegacyContact.send_files` or
    :meth:`.LegacyParticipant.send_files`
    """

    path: Path | str | None = None
    name: str | None = None
    stream: IO[bytes] | None = None
    aio_stream: AsyncIterator[bytes] | None = None
    data: bytes | None = None
    content_type: str | None = None
    legacy_file_id: str | int | None = None
    url: str | None = None
    caption: str | None = None
    disposition: AttachmentDisposition | None = None
    """
    A caption for this specific image. For a global caption for a list of attachments,
    use the ``body`` parameter of :meth:`.AttachmentMixin.send_files`
    """

    def __post_init__(self) -> None:
        if all(
            x is None
            for x in (self.path, self.stream, self.data, self.url, self.aio_stream)
        ):
            raise TypeError("There is not data in this attachment", self)
        if isinstance(self.path, str):
            self.path = Path(self.path)

    def format_for_user(self) -> str:
        if self.name:
            name = self.name
        elif self.path:
            name = self.path.name  # type:ignore[union-attr]
        elif self.url:
            name = self.url
        else:
            name = ""

        if self.caption:
            if name:
                name = f"{name}: {self.caption}"
            else:
                name = self.caption

        return name

    def __str__(self):
        attrs = ", ".join(
            f"{f.name}={getattr(self, f.name)!r}"
            for f in fields(self)
            if getattr(self, f.name) is not None and f.name != "data"
        )
        if self.data is not None:
            data_str = f"data=<{len(self.data)} bytes>"
            to_join = (attrs, data_str) if attrs else (data_str,)
            attrs = ", ".join(to_join)
        return f"Attachment({attrs})"


class MucType(IntEnum):
    """
    The type of group, private, public, anonymous or not.
    """

    GROUP = 0
    """
    A private group, members-only and non-anonymous, eg a family group.
    """
    CHANNEL = 1
    """
    A public group, aka an anonymous channel.
    """
    CHANNEL_NON_ANONYMOUS = 2
    """
    A public group where participants' legacy IDs are visible to everybody.
    """


PseudoPresenceShow = PresenceShows | Literal[""]


MessageOrPresenceTypeVar = TypeVar("MessageOrPresenceTypeVar", bound=Message | Presence)


class LinkPreview(NamedTuple):
    about: str
    title: str | None
    description: str | None
    url: str | None
    image: str | None
    type: str | None
    site_name: str | None


class Mention(NamedTuple):
    contact: "LegacyContact[Any]"
    start: int
    end: int


class Hat(NamedTuple):
    uri: str
    title: str
    hue: float | None = None


class UserPreferences(TypedDict):
    sync_avatar: bool
    sync_presence: bool


class MamMetadata(NamedTuple):
    id: str
    sent_on: datetime


class HoleBound(NamedTuple):
    id: int | str
    timestamp: datetime


class CachedPresence(NamedTuple):
    last_seen: datetime | None = None
    ptype: PresenceTypes | None = None
    pstatus: str | None = None
    pshow: PresenceShows | None = None


class Sticker(NamedTuple):
    path: Path
    content_type: str | None
    hashes: dict[str, str]


class Avatar(NamedTuple):
    path: Path | None = None
    unique_id: str | int | None = None
    url: str | None = None
    data: bytes | None = None
