# SPDX-License-Identifier: MIT

from __future__ import annotations

import datetime
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type

from .enums import ExpireBehaviour, try_enum
from .user import User
from .utils import (
    MISSING,
    _get_as_snowflake,
    deprecated,
    parse_time,
    snowflake_time,
    warn_deprecated,
)

__all__ = (
    "IntegrationAccount",
    "IntegrationApplication",
    "PartialIntegration",
    "Integration",
    "StreamIntegration",
    "BotIntegration",
)

if TYPE_CHECKING:
    from .guild import Guild
    from .role import Role
    from .types.integration import (
        BotIntegration as BotIntegrationPayload,
        Integration as IntegrationPayload,
        IntegrationAccount as IntegrationAccountPayload,
        IntegrationApplication as IntegrationApplicationPayload,
        IntegrationType,
        PartialIntegration as PartialIntegrationPayload,
        StreamIntegration as StreamIntegrationPayload,
    )


class IntegrationAccount:
    """Represents an integration account.

    .. versionadded:: 1.4

    Attributes
    ----------
    id: :class:`str`
        The account ID.
    name: :class:`str`
        The account name.
    """

    __slots__ = ("id", "name")

    def __init__(self, data: IntegrationAccountPayload) -> None:
        self.id: str = data["id"]
        self.name: str = data["name"]

    def __repr__(self) -> str:
        return f"<IntegrationAccount id={self.id} name={self.name!r}>"


class PartialIntegration:
    """Represents a partial guild integration.

    .. versionadded:: 2.6

    Attributes
    ----------
    id: :class:`int`
        The integration ID.
    name: :class:`str`
        The integration name.
    guild: :class:`Guild`
        The guild of the integration.
    type: :class:`str`
        The integration type (i.e. ``twitch``).
    account: :class:`IntegrationAccount`
        The account linked to this integration.
    application_id: Optional[:class:`int`]
        The ID of the application tied to this integration.
    """

    __slots__ = (
        "guild",
        "id",
        "name",
        "type",
        "account",
        "application_id",
    )

    def __init__(self, *, data: PartialIntegrationPayload, guild: Guild) -> None:
        self.guild = guild
        self._from_data(data)

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} id={self.id} name={self.name!r}>"

    def _from_data(self, data: PartialIntegrationPayload) -> None:
        self.id: int = int(data["id"])
        self.type: IntegrationType = data["type"]
        self.name: str = data["name"]
        self.account: IntegrationAccount = IntegrationAccount(data["account"])
        self.application_id: Optional[int] = _get_as_snowflake(data, "application_id")

    @property
    def created_at(self) -> datetime.datetime:
        """:class:`datetime.datetime`: Returns the integration's
        (*not* the associated application's) creation time in UTC.

        .. versionadded:: 2.10
        """
        return snowflake_time(self.id)


class Integration(PartialIntegration):
    """Represents a guild integration.

    .. versionadded:: 1.4

    Attributes
    ----------
    id: :class:`int`
        The integration ID.
    name: :class:`str`
        The integration name.
    guild: :class:`Guild`
        The guild of the integration.
    type: :class:`str`
        The integration type (i.e. Twitch).
    enabled: :class:`bool`
        Whether the integration is currently enabled.
    account: :class:`IntegrationAccount`
        The account linked to this integration.
    user: Optional[:class:`User`]
        The user that added this integration.
    """

    __slots__ = (
        "_state",
        "user",
        "enabled",
    )

    def _from_data(self, data: IntegrationPayload) -> None:
        super()._from_data(data)
        self._state = self.guild._state

        user = data.get("user")
        self.user = User(state=self._state, data=user) if user else None
        self.enabled: bool = data["enabled"]

    @deprecated("Guild.leave")
    async def delete(self, *, reason: Optional[str] = None) -> None:
        """|coro|

        .. deprecated:: 2.5
            Can only be used on the application's own integration and is therefore
            equivalent to leaving the guild.

        Deletes the integration.

        You must have :attr:`~Permissions.manage_guild` permission to
        use this.

        Parameters
        ----------
        reason: Optional[:class:`str`]
            The reason the integration was deleted. Shows up on the audit log.

            .. versionadded:: 2.0

        Raises
        ------
        Forbidden
            You do not have permission to delete the integration.
        HTTPException
            Deleting the integration failed.
        """
        await self._state.http.delete_integration(self.guild.id, self.id, reason=reason)


class StreamIntegration(Integration):
    """Represents a stream integration for Twitch or YouTube.

    .. versionadded:: 2.0

    Attributes
    ----------
    id: :class:`int`
        The integration ID.
    name: :class:`str`
        The integration name.
    guild: :class:`Guild`
        The guild of the integration.
    type: :class:`str`
        The integration type (i.e. Twitch).
    enabled: :class:`bool`
        Whether the integration is currently enabled.
    syncing: :class:`bool`
        Whether the integration is currently syncing.
    enable_emoticons: Optional[:class:`bool`]
        Whether emoticons should be synced for this integration (currently twitch only).
    expire_behaviour: :class:`ExpireBehaviour`
        The behaviour of expiring subscribers. Aliased to ``expire_behavior`` as well.
    expire_grace_period: :class:`int`
        The grace period (in days) for expiring subscribers.
    user: :class:`User`
        The user for the integration.
    account: :class:`IntegrationAccount`
        The integration account information.
    synced_at: :class:`datetime.datetime`
        An aware UTC datetime representing when the integration was last synced.
    """

    __slots__ = (
        "revoked",
        "expire_behaviour",
        "expire_grace_period",
        "synced_at",
        "_role_id",
        "syncing",
        "enable_emoticons",
        "subscriber_count",
    )

    def _from_data(self, data: StreamIntegrationPayload) -> None:
        super()._from_data(data)
        self.revoked: bool = data["revoked"]
        self.expire_behaviour: ExpireBehaviour = try_enum(ExpireBehaviour, data["expire_behavior"])
        self.expire_grace_period: int = data["expire_grace_period"]
        self.synced_at: datetime.datetime = parse_time(data["synced_at"])
        self._role_id: Optional[int] = _get_as_snowflake(data, "role_id")
        self.syncing: bool = data["syncing"]
        self.enable_emoticons: bool = data["enable_emoticons"]
        self.subscriber_count: int = data["subscriber_count"]

    @property
    def expire_behavior(self) -> ExpireBehaviour:
        """:class:`ExpireBehaviour`: An alias for :attr:`expire_behaviour`."""
        return self.expire_behaviour

    @property
    def role(self) -> Optional[Role]:
        """Optional[:class:`Role`] The role which the integration uses for subscribers."""
        return self.guild.get_role(self._role_id)  # type: ignore

    @deprecated()
    async def edit(
        self,
        *,
        expire_behaviour: ExpireBehaviour = MISSING,
        expire_grace_period: int = MISSING,
        enable_emoticons: bool = MISSING,
    ) -> None:
        """|coro|

        .. deprecated:: 2.5
            No longer supported, bots cannot use this endpoint anymore.

        Edits the integration.

        You must have :attr:`~Permissions.manage_guild` permission to
        use this.

        .. versionchanged:: 2.6
            Raises :exc:`TypeError` instead of ``InvalidArgument``.

        Parameters
        ----------
        expire_behaviour: :class:`ExpireBehaviour`
            The behaviour when an integration subscription lapses. Aliased to ``expire_behavior`` as well.
        expire_grace_period: :class:`int`
            The period (in days) where the integration will ignore lapsed subscriptions.
        enable_emoticons: :class:`bool`
            Where emoticons should be synced for this integration (currently twitch only).

        Raises
        ------
        Forbidden
            You do not have permission to edit the integration.
        HTTPException
            Editing the guild failed.
        TypeError
            ``expire_behaviour`` did not receive a :class:`ExpireBehaviour`.
        """
        payload: Dict[str, Any] = {}
        if expire_behaviour is not MISSING:
            if not isinstance(expire_behaviour, ExpireBehaviour):
                raise TypeError("expire_behaviour field must be of type ExpireBehaviour")

            payload["expire_behavior"] = expire_behaviour.value

        if expire_grace_period is not MISSING:
            payload["expire_grace_period"] = expire_grace_period

        if enable_emoticons is not MISSING:
            payload["enable_emoticons"] = enable_emoticons

        # This endpoint is undocumented.
        # Unsure if it returns the data or not as a result
        await self._state.http.edit_integration(self.guild.id, self.id, **payload)

    @deprecated()
    async def sync(self) -> None:
        """|coro|

        .. deprecated:: 2.5
            No longer supported, bots cannot use this endpoint anymore.

        Syncs the integration.

        You must have :attr:`~Permissions.manage_guild` permission to
        use this.

        Raises
        ------
        Forbidden
            You do not have permission to sync the integration.
        HTTPException
            Syncing the integration failed.
        """
        await self._state.http.sync_integration(self.guild.id, self.id)
        self.synced_at = datetime.datetime.now(datetime.timezone.utc)


class IntegrationApplication:
    """Represents an application for a bot integration.

    .. versionadded:: 2.0

    Attributes
    ----------
    id: :class:`int`
        The application's ID.
    name: :class:`str`
        The application's name.
    icon: Optional[:class:`str`]
        The application's icon hash.
    description: :class:`str`
        The application's description. Can be an empty string.
    user: Optional[:class:`User`]
        The bot user associated with this application.
    """

    __slots__ = (
        "id",
        "name",
        "icon",
        "description",
        "_summary",
        "user",
    )

    def __init__(self, *, data: IntegrationApplicationPayload, state) -> None:
        self.id: int = int(data["id"])
        self.name: str = data["name"]
        self.icon: Optional[str] = data["icon"]
        self.description: str = data["description"]
        self._summary: str = data.get("summary", "")
        user = data.get("bot")
        self.user: Optional[User] = User(state=state, data=user) if user else None

    @property
    def summary(self) -> str:
        """:class:`str`: The application's summary. Can be an empty string.

        .. deprecated:: 2.5

            This field is deprecated by discord and is now always blank. Consider using :attr:`.description` instead.
        """
        warn_deprecated(
            "summary is deprecated and will be removed in a future version. Consider using description instead.",
            stacklevel=2,
        )
        return self._summary


class BotIntegration(Integration):
    """Represents a bot integration on Discord.

    .. versionadded:: 2.0

    Attributes
    ----------
    id: :class:`int`
        The integration ID.
    name: :class:`str`
        The integration name.
    guild: :class:`Guild`
        The guild of the integration.
    type: :class:`str`
        The integration type (i.e. Twitch).
    enabled: :class:`bool`
        Whether the integration is currently enabled.
    user: :class:`User`
        The user that added this integration.
    account: :class:`IntegrationAccount`
        The integration account information.
    application: :class:`IntegrationApplication`
        The application tied to this integration.
    scopes: List[:class:`str`]
        The OAuth2 scopes the application has been authorized for.

        .. versionadded:: 2.6
    """

    __slots__ = ("application", "scopes")

    def _from_data(self, data: BotIntegrationPayload) -> None:
        super()._from_data(data)
        self.application: IntegrationApplication = IntegrationApplication(
            data=data["application"], state=self._state
        )
        self.scopes: List[str] = data.get("scopes") or []

    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__} id={self.id}"
            f" name={self.name!r} scopes={self.scopes!r}>"
        )


def _integration_factory(value: str) -> Tuple[Type[Integration], str]:
    if value == "discord":
        return BotIntegration, value
    elif value in ("twitch", "youtube"):
        return StreamIntegration, value
    else:
        return Integration, value
