#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program.  If not, see [http://www.gnu.org/licenses/].
"""This module contains the classes that represent Telegram MessageOigin."""
import datetime
from typing import TYPE_CHECKING, Dict, Final, Optional, Type

from telegram import constants
from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict

if TYPE_CHECKING:
    from telegram import Bot


class MessageOrigin(TelegramObject):
    """
    Base class for telegram MessageOrigin object, it can be one of:

    * :class:`MessageOriginUser`
    * :class:`MessageOriginHiddenUser`
    * :class:`MessageOriginChat`
    * :class:`MessageOriginChannel`

    Objects of this class are comparable in terms of equality. Two objects of this class are
    considered equal, if their :attr:`type` and :attr:`date` are equal.

    .. versionadded:: 20.8

    Args:
        type (:obj:`str`): Type of the message origin, can be on of:
            :attr:`~telegram.MessageOrigin.USER`, :attr:`~telegram.MessageOrigin.HIDDEN_USER`,
            :attr:`~telegram.MessageOrigin.CHAT`, or :attr:`~telegram.MessageOrigin.CHANNEL`.
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|

    Attributes:
        type (:obj:`str`): Type of the message origin, can be on of:
            :attr:`~telegram.MessageOrigin.USER`, :attr:`~telegram.MessageOrigin.HIDDEN_USER`,
            :attr:`~telegram.MessageOrigin.CHAT`, or :attr:`~telegram.MessageOrigin.CHANNEL`.
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
    """

    __slots__ = (
        "date",
        "type",
    )

    USER: Final[str] = constants.MessageOriginType.USER
    """:const:`telegram.constants.MessageOriginType.USER`"""
    HIDDEN_USER: Final[str] = constants.MessageOriginType.HIDDEN_USER
    """:const:`telegram.constants.MessageOriginType.HIDDEN_USER`"""
    CHAT: Final[str] = constants.MessageOriginType.CHAT
    """:const:`telegram.constants.MessageOriginType.CHAT`"""
    CHANNEL: Final[str] = constants.MessageOriginType.CHANNEL
    """:const:`telegram.constants.MessageOriginType.CHANNEL`"""

    def __init__(
        self,
        type: str,  # pylint: disable=W0622
        date: datetime.datetime,
        *,
        api_kwargs: Optional[JSONDict] = None,
    ):
        super().__init__(api_kwargs=api_kwargs)
        # Required by all subclasses
        self.type: str = enum.get_member(constants.MessageOriginType, type, type)
        self.date: datetime.datetime = date

        self._id_attrs = (
            self.type,
            self.date,
        )
        self._freeze()

    @classmethod
    def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["MessageOrigin"]:
        """Converts JSON data to the appropriate :class:`MessageOrigin` object, i.e. takes
        care of selecting the correct subclass.
        """
        data = cls._parse_data(data)

        if not data:
            return None

        _class_mapping: Dict[str, Type[MessageOrigin]] = {
            cls.USER: MessageOriginUser,
            cls.HIDDEN_USER: MessageOriginHiddenUser,
            cls.CHAT: MessageOriginChat,
            cls.CHANNEL: MessageOriginChannel,
        }
        if cls is MessageOrigin and data.get("type") in _class_mapping:
            return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)

        loc_tzinfo = extract_tzinfo_from_defaults(bot)
        data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)

        if "sender_user" in data:
            data["sender_user"] = User.de_json(data.get("sender_user"), bot)

        if "sender_chat" in data:
            data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)

        if "chat" in data:
            data["chat"] = Chat.de_json(data.get("chat"), bot)

        return super().de_json(data=data, bot=bot)


class MessageOriginUser(MessageOrigin):
    """
    The message was originally sent by a known user.

    .. versionadded:: 20.8

    Args:
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
        sender_user (:class:`telegram.User`): User that sent the message originally.

    Attributes:
        type (:obj:`str`): Type of the message origin. Always
            :tg-const:`~telegram.MessageOrigin.USER`.
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
        sender_user (:class:`telegram.User`): User that sent the message originally.
    """

    __slots__ = ("sender_user",)

    def __init__(
        self,
        date: datetime.datetime,
        sender_user: User,
        *,
        api_kwargs: Optional[JSONDict] = None,
    ):
        super().__init__(type=self.USER, date=date, api_kwargs=api_kwargs)

        with self._unfrozen():
            self.sender_user: User = sender_user


class MessageOriginHiddenUser(MessageOrigin):
    """
    The message was originally sent by an unknown user.

    .. versionadded:: 20.8

    Args:
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
        sender_user_name (:obj:`str`): Name of the user that sent the message originally.

    Attributes:
        type (:obj:`str`): Type of the message origin. Always
            :tg-const:`~telegram.MessageOrigin.HIDDEN_USER`.
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
        sender_user_name (:obj:`str`): Name of the user that sent the message originally.
    """

    __slots__ = ("sender_user_name",)

    def __init__(
        self,
        date: datetime.datetime,
        sender_user_name: str,
        *,
        api_kwargs: Optional[JSONDict] = None,
    ):
        super().__init__(type=self.HIDDEN_USER, date=date, api_kwargs=api_kwargs)

        with self._unfrozen():
            self.sender_user_name: str = sender_user_name


class MessageOriginChat(MessageOrigin):
    """
    The message was originally sent on behalf of a chat to a group chat.

    .. versionadded:: 20.8

    Args:
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
        sender_chat (:class:`telegram.Chat`): Chat that sent the message originally.
        author_signature (:obj:`str`, optional): For messages originally sent by an anonymous chat
            administrator, original message author signature

    Attributes:
        type (:obj:`str`): Type of the message origin. Always
            :tg-const:`~telegram.MessageOrigin.CHAT`.
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
        sender_chat (:class:`telegram.Chat`): Chat that sent the message originally.
        author_signature (:obj:`str`): Optional. For messages originally sent by an anonymous chat
            administrator, original message author signature
    """

    __slots__ = (
        "author_signature",
        "sender_chat",
    )

    def __init__(
        self,
        date: datetime.datetime,
        sender_chat: Chat,
        author_signature: Optional[str] = None,
        *,
        api_kwargs: Optional[JSONDict] = None,
    ):
        super().__init__(type=self.CHAT, date=date, api_kwargs=api_kwargs)

        with self._unfrozen():
            self.sender_chat: Chat = sender_chat
            self.author_signature: Optional[str] = author_signature


class MessageOriginChannel(MessageOrigin):
    """
    The message was originally sent to a channel chat.

    .. versionadded:: 20.8

    Args:
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
        chat (:class:`telegram.Chat`): Channel chat to which the message was originally sent.
        message_id (:obj:`int`): Unique message identifier inside the chat.
        author_signature (:obj:`str`, optional): Signature of the original post author.

    Attributes:
        type (:obj:`str`): Type of the message origin. Always
            :tg-const:`~telegram.MessageOrigin.CHANNEL`.
        date (:obj:`datetime.datetime`): Date the message was sent originally.
            |datetime_localization|
        chat (:class:`telegram.Chat`): Channel chat to which the message was originally sent.
        message_id (:obj:`int`): Unique message identifier inside the chat.
        author_signature (:obj:`str`): Optional. Signature of the original post author.
    """

    __slots__ = (
        "author_signature",
        "chat",
        "message_id",
    )

    def __init__(
        self,
        date: datetime.datetime,
        chat: Chat,
        message_id: int,
        author_signature: Optional[str] = None,
        *,
        api_kwargs: Optional[JSONDict] = None,
    ):
        super().__init__(type=self.CHANNEL, date=date, api_kwargs=api_kwargs)

        with self._unfrozen():
            self.chat: Chat = chat
            self.message_id: int = message_id
            self.author_signature: Optional[str] = author_signature
