"""Provide the helper classes."""
from json import dumps
from typing import TYPE_CHECKING, Generator, List, Optional, Union

from ..const import API_PATH
from ..util import _deprecate_args
from .base import PRAWBase
from .reddit.draft import Draft
from .reddit.live import LiveThread
from .reddit.multi import Multireddit, Subreddit

if TYPE_CHECKING:  # pragma: no cover
    import praw


class DraftHelper(PRAWBase):
    r"""Provide a set of functions to interact with :class:`.Draft` instances.

    .. note::

        The methods provided by this class will only work on the currently authenticated
        user's :class:`.Draft`\ s.

    """

    def __call__(
        self, draft_id: Optional[str] = None
    ) -> Union[List["praw.models.Draft"], "praw.models.Draft"]:
        """Return a list of :class:`.Draft` instances.

        :param draft_id: When provided, return :class:`.Draft` instance (default:
            ``None``).

        :returns: A :class:`.Draft` instance if ``draft_id`` is provided. Otherwise, a
            list of :class:`.Draft` objects.

        .. note::

            Drafts fetched using a specific draft ID are lazily loaded, so you might
            have to access an attribute to get all the expected attributes.

        This method can be used to fetch a specific draft by ID, like so:

        .. code-block:: python

            draft_id = "124862bc-e1e9-11eb-aa4f-e68667a77cbb"
            draft = reddit.drafts(draft_id)
            print(draft)

        """
        if draft_id is not None:
            return Draft(self._reddit, id=draft_id)
        return self._draft_list()

    def _draft_list(self) -> List["praw.models.Draft"]:
        """Get a list of :class:`.Draft` instances.

        :returns: A list of :class:`.Draft` instances.

        """
        return self._reddit.get(API_PATH["drafts"], params={"md_body": True})

    def create(
        self,
        *,
        flair_id: Optional[str] = None,
        flair_text: Optional[str] = None,
        is_public_link: bool = False,
        nsfw: bool = False,
        original_content: bool = False,
        selftext: Optional[str] = None,
        send_replies: bool = True,
        spoiler: bool = False,
        subreddit: Optional[
            Union[str, "praw.models.Subreddit", "praw.models.UserSubreddit"]
        ] = None,
        title: Optional[str] = None,
        url: Optional[str] = None,
        **draft_kwargs,
    ) -> "praw.models.Draft":
        """Create a new :class:`.Draft`.

        :param flair_id: The flair template to select (default: ``None``).
        :param flair_text: If the template's ``flair_text_editable`` value is ``True``,
            this value will set a custom text (default: ``None``). ``flair_id`` is
            required when ``flair_text`` is provided.
        :param is_public_link: Whether to enable public viewing of the draft before it
            is submitted (default: ``False``).
        :param nsfw: Whether the draft should be marked NSFW (default: ``False``).
        :param original_content: Whether the submission should be marked as original
            content (default: ``False``).
        :param selftext: The Markdown formatted content for a text submission draft. Use
            ``None`` to make a title-only submission draft (default: ``None``).
            ``selftext`` can not be provided if ``url`` is provided.
        :param send_replies: When ``True``, messages will be sent to the submission
            author when comments are made to the submission (default: ``True``).
        :param spoiler: Whether the submission should be marked as a spoiler (default:
            ``False``).
        :param subreddit: The subreddit to create the draft for. This accepts a
            subreddit display name, :class:`.Subreddit` object, or
            :class:`.UserSubreddit` object. If ``None``, the :class:`.UserSubreddit` of
            currently authenticated user will be used (default: ``None``).
        :param title: The title of the draft (default: ``None``).
        :param url: The URL for a ``link`` submission draft (default: ``None``). ``url``
            can not be provided if ``selftext`` is provided.

        Additional keyword arguments can be provided to handle new parameters as Reddit
        introduces them.

        :returns: The new :class:`.Draft` object.

        """
        if selftext and url:
            raise TypeError("Exactly one of `selftext` or `url` must be provided.")
        if isinstance(subreddit, str):
            subreddit = self._reddit.subreddit(subreddit)

        data = Draft._prepare_data(
            flair_id=flair_id,
            flair_text=flair_text,
            is_public_link=is_public_link,
            nsfw=nsfw,
            original_content=original_content,
            selftext=selftext,
            send_replies=send_replies,
            spoiler=spoiler,
            subreddit=subreddit,
            title=title,
            url=url,
            **draft_kwargs,
        )
        return self._reddit.post(API_PATH["draft"], data=data)


class LiveHelper(PRAWBase):
    r"""Provide a set of functions to interact with :class:`.LiveThread`\ s."""

    def __call__(
        self, id: str
    ) -> "praw.models.LiveThread":  # pylint: disable=invalid-name,redefined-builtin
        """Return a new lazy instance of :class:`.LiveThread`.

        This method is intended to be used as:

        .. code-block:: python

            livethread = reddit.live("ukaeu1ik4sw5")

        :param id: A live thread ID, e.g., ``ukaeu1ik4sw5``.

        """
        return LiveThread(self._reddit, id=id)

    def info(self, ids: List[str]) -> Generator["praw.models.LiveThread", None, None]:
        """Fetch information about each live thread in ``ids``.

        :param ids: A list of IDs for a live thread.

        :returns: A generator that yields :class:`.LiveThread` instances.

        Live threads that cannot be matched will not be generated. Requests will be
        issued in batches for each 100 IDs.

        .. warning::

            Unlike :meth:`.Reddit.info`, the output of this method may not reflect the
            order of input.

        Usage:

        .. code-block:: python

            ids = ["3rgnbke2rai6hen7ciytwcxadi", "sw7bubeycai6hey4ciytwamw3a", "t8jnufucss07"]
            for thread in reddit.live.info(ids):
                print(thread.title)

        """
        if not isinstance(ids, list):
            raise TypeError("ids must be a list")

        def generator():
            for position in range(0, len(ids), 100):
                ids_chunk = ids[position : position + 100]
                url = API_PATH["live_info"].format(ids=",".join(ids_chunk))
                params = {"limit": 100}  # 25 is used if not specified
                for result in self._reddit.get(url, params=params):
                    yield result

        return generator()

    @_deprecate_args("title", "description", "nsfw", "resources")
    def create(
        self,
        title: str,
        *,
        description: Optional[str] = None,
        nsfw: bool = False,
        resources: str = None,
    ) -> "praw.models.LiveThread":
        """Create a new :class:`.LiveThread`.

        :param title: The title of the new :class:`.LiveThread`.
        :param description: The new :class:`.LiveThread`'s description.
        :param nsfw: Indicate whether this thread is not safe for work (default:
            ``False``).
        :param resources: Markdown formatted information that is useful for the
            :class:`.LiveThread`.

        :returns: The new :class`.LiveThread` object.

        """
        return self._reddit.post(
            API_PATH["livecreate"],
            data={
                "description": description,
                "nsfw": nsfw,
                "resources": resources,
                "title": title,
            },
        )

    def now(self) -> Optional["praw.models.LiveThread"]:
        """Get the currently featured live thread.

        :returns: The :class:`.LiveThread` object, or ``None`` if there is no currently
            featured live thread.

        Usage:

        .. code-block:: python

            thread = reddit.live.now()  # LiveThread object or None

        """
        return self._reddit.get(API_PATH["live_now"])


class MultiredditHelper(PRAWBase):
    """Provide a set of functions to interact with multireddits."""

    @_deprecate_args("redditor", "name")
    def __call__(
        self, *, name: str, redditor: Union[str, "praw.models.Redditor"]
    ) -> "praw.models.Multireddit":
        """Return a lazy instance of :class:`.Multireddit`.

        :param name: The name of the multireddit.
        :param redditor: A redditor name or :class:`.Redditor` instance who owns the
            multireddit.

        """
        path = f"/user/{redditor}/m/{name}"
        return Multireddit(self._reddit, _data={"name": name, "path": path})

    @_deprecate_args(
        "display_name",
        "subreddits",
        "description_md",
        "icon_name",
        "key_color",
        "visibility",
        "weighting_scheme",
    )
    def create(
        self,
        *,
        description_md: Optional[str] = None,
        display_name: str,
        icon_name: Optional[str] = None,
        key_color: Optional[str] = None,
        subreddits: Union[str, "praw.models.Subreddit"],
        visibility: str = "private",
        weighting_scheme: str = "classic",
    ) -> "praw.models.Multireddit":
        """Create a new :class:`.Multireddit`.

        :param display_name: The display name for the new multireddit.
        :param subreddits: Subreddits to add to the new multireddit. Can be a list of
            either :class:`.Subreddit` instances or subreddit display names.
        :param description_md: Description for the new multireddit, formatted in
            markdown.
        :param icon_name: Can be one of: ``"art and design"``, ``"ask"``, ``"books"``,
            ``"business"``, ``"cars"``, ``"comics"``, ``"cute animals"``, ``"diy"``,
            ``"entertainment"``, ``"food and drink"``, ``"funny"``, ``"games"``,
            ``"grooming"``, ``"health"``, ``"life advice"``, ``"military"``, ``"models
            pinup"``, ``"music"``, ``"news"``, ``"philosophy"``, ``"pictures and
            gifs"``, ``"science"``, ``"shopping"``, ``"sports"``, ``"style"``,
            ``"tech"``, ``"travel"``, ``"unusual stories"``, ``"video"``, or ``None``.
        :param key_color: RGB hex color code of the form ``"#FFFFFF"``.
        :param visibility: Can be one of: ``"hidden"``, ``"private"``, or ``"public"``
            (default: ``"private"``).
        :param weighting_scheme: Can be one of: ``"classic"`` or ``"fresh"`` (default:
            ``"classic"``).

        :returns: The new :class:`.Multireddit` object.

        """
        model = {
            "description_md": description_md,
            "display_name": display_name,
            "icon_name": icon_name,
            "key_color": key_color,
            "subreddits": [{"name": str(sub)} for sub in subreddits],
            "visibility": visibility,
            "weighting_scheme": weighting_scheme,
        }
        return self._reddit.post(
            API_PATH["multireddit_base"], data={"model": dumps(model)}
        )


class SubredditHelper(PRAWBase):
    """Provide a set of functions to interact with Subreddits."""

    def __call__(self, display_name: str) -> "praw.models.Subreddit":
        """Return a lazy instance of :class:`.Subreddit`.

        :param display_name: The name of the subreddit.

        """
        lower_name = display_name.lower()

        if lower_name == "random":
            return self._reddit.random_subreddit()
        if lower_name == "randnsfw":
            return self._reddit.random_subreddit(nsfw=True)

        return Subreddit(self._reddit, display_name=display_name)

    @_deprecate_args("name", "title", "link_type", "subreddit_type", "wikimode")
    def create(
        self,
        name: str,
        *,
        link_type: str = "any",
        subreddit_type: str = "public",
        title: Optional[str] = None,
        wikimode: str = "disabled",
        **other_settings: Optional[str],
    ) -> "praw.models.Subreddit":
        """Create a new :class:`.Subreddit`.

        :param name: The name for the new subreddit.
        :param link_type: The types of submissions users can make. One of ``"any"``,
            ``"link"``, or ``"self"`` (default: ``"any"``).
        :param subreddit_type: One of ``"archived"``, ``"employees_only"``,
            ``"gold_only"``, ``"gold_restricted"``, ``"private"``, ``"public"``, or
            ``"restricted"`` (default: ``"public"``).
        :param title: The title of the subreddit. When ``None`` or ``""`` use the value
            of ``name``.
        :param wikimode: One of ``"anyone"``, ``"disabled"``, or ``"modonly"`` (default:
            ``"disabled"``).

        Any keyword parameters not provided, or set explicitly to ``None``, will take on
        a default value assigned by the Reddit server.

        .. seealso::

            :meth:`~.SubredditModeration.update` for documentation of other available
            settings.

        """
        Subreddit._create_or_update(
            _reddit=self._reddit,
            link_type=link_type,
            name=name,
            subreddit_type=subreddit_type,
            title=title or name,
            wikimode=wikimode,
            **other_settings,
        )
        return self(name)
