"""Provide the WikiPage class."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from praw.const import API_PATH
from praw.models.listing.generator import ListingGenerator
from praw.models.reddit.base import RedditBase
from praw.models.reddit.redditor import Redditor
from praw.util.cache import cachedproperty

if TYPE_CHECKING:
    from collections.abc import Iterator

    import praw.models


class WikiPageModeration:
    """Provides a set of moderation functions for a :class:`.WikiPage`.

    For example, to add u/spez as an editor on the wikipage ``"praw_test"`` try:

    .. code-block:: python

        reddit.subreddit("test").wiki["praw_test"].mod.add("spez")

    """

    def __init__(self, wikipage: WikiPage) -> None:
        """Initialize a :class:`.WikiPageModeration` instance.

        :param wikipage: The wikipage to moderate.

        """
        self.wikipage = wikipage

    def add(self, redditor: praw.models.Redditor) -> None:
        """Add an editor to this :class:`.WikiPage`.

        :param redditor: A redditor name or :class:`.Redditor` instance.

        To add u/spez as an editor on the wikipage ``"praw_test"`` try:

        .. code-block:: python

            reddit.subreddit("test").wiki["praw_test"].mod.add("spez")

        """
        data = {"page": self.wikipage.name, "username": str(redditor)}
        url = API_PATH["wiki_page_editor"].format(subreddit=self.wikipage.subreddit, method="add")
        self.wikipage._reddit.post(url, data=data)

    def remove(self, redditor: praw.models.Redditor) -> None:
        """Remove an editor from this :class:`.WikiPage`.

        :param redditor: A redditor name or :class:`.Redditor` instance.

        To remove u/spez as an editor on the wikipage ``"praw_test"`` try:

        .. code-block:: python

            reddit.subreddit("test").wiki["praw_test"].mod.remove("spez")

        """
        data = {"page": self.wikipage.name, "username": str(redditor)}
        url = API_PATH["wiki_page_editor"].format(subreddit=self.wikipage.subreddit, method="del")
        self.wikipage._reddit.post(url, data=data)

    def revert(self) -> None:
        """Revert a wikipage back to a specific revision.

        To revert the page ``"praw_test"`` in r/test to revision ``"1234abc"``, try

        .. code-block:: python

            reddit.subreddit("test").wiki["praw_test"].revision("1234abc").mod.revert()

        .. note::

            When you attempt to revert the page ``config/stylesheet``, Reddit checks to
            see if the revision being reverted to passes the CSS filter. If the check
            fails, then the revision attempt will also fail, and a
            ``prawcore.Forbidden`` exception will be raised. For example, you can't
            revert to a revision that contains a link to ``url(%%PRAW%%)`` if there is
            no image named ``PRAW`` on the current stylesheet.

            Here is an example of how to look for this type of error:

            .. code-block:: python

                from prawcore.exceptions import Forbidden

                try:
                    reddit.subreddit("test").wiki["config/stylesheet"].revision("1234abc").mod.revert()
                except Forbidden as exception:
                    try:
                        exception.response.json()
                    except ValueError:
                        exception.response.text

            If the error occurs, the output will look something like

            .. code-block:: python

                {"reason": "INVALID_CSS", "message": "Forbidden", "explanation": "%(css_error)s"}

        """
        self.wikipage._reddit.post(
            API_PATH["wiki_revert"].format(subreddit=self.wikipage.subreddit),
            data={
                "page": self.wikipage.name,
                "revision": self.wikipage._revision,
            },
        )

    def settings(self) -> dict[str, Any]:
        """Return the settings for this :class:`.WikiPage`."""
        url = API_PATH["wiki_page_settings"].format(subreddit=self.wikipage.subreddit, page=self.wikipage.name)
        return self.wikipage._reddit.get(url)["data"]

    def update(self, *, listed: bool, permlevel: int, **other_settings: Any) -> dict[str, Any]:
        """Update the settings for this :class:`.WikiPage`.

        :param listed: Show this page on page list.
        :param permlevel: Who can edit this page? ``0`` use subreddit wiki permissions,
            ``1`` only approved wiki contributors for this page may edit (see
            :meth:`.WikiPageModeration.add`), ``2`` only mods may edit and view.
        :param other_settings: Additional keyword arguments to pass.

        :returns: The updated WikiPage settings.

        To set the wikipage ``"praw_test"`` in r/test to mod only and disable it from
        showing in the page list, try:

        .. code-block:: python

            reddit.subreddit("test").wiki["praw_test"].mod.update(listed=False, permlevel=2)

        """
        other_settings.update({"listed": listed, "permlevel": permlevel})
        url = API_PATH["wiki_page_settings"].format(subreddit=self.wikipage.subreddit, page=self.wikipage.name)
        return self.wikipage._reddit.post(url, data=other_settings)["data"]


class WikiPage(RedditBase):
    """An individual :class:`.WikiPage` object.

    .. include:: ../../typical_attributes.rst

    ================= =================================================================
    Attribute         Description
    ================= =================================================================
    ``content_html``  The contents of the wiki page, as HTML.
    ``content_md``    The contents of the wiki page, as Markdown.
    ``may_revise``    A ``bool`` representing whether or not the authenticated user may
                      edit the wiki page.
    ``name``          The name of the wiki page.
    ``revision_by``   The :class:`.Redditor` who authored this revision of the wiki
                      page.
    ``revision_date`` The time of this revision, in `Unix Time`_.
    ``subreddit``     The :class:`.Subreddit` this wiki page belongs to.
    ================= =================================================================

    .. _unix time: https://en.wikipedia.org/wiki/Unix_time

    """

    __hash__ = RedditBase.__hash__

    @staticmethod
    def _revision_generator(
        *,
        generator_kwargs: dict[str, Any],
        subreddit: praw.models.Subreddit,
        url: str,
    ) -> Iterator[dict[str, Redditor | WikiPage | str | int | bool | None]]:
        for revision in ListingGenerator(subreddit._reddit, url, **generator_kwargs):
            if revision["author"] is not None:
                revision["author"] = Redditor(subreddit._reddit, _data=revision["author"]["data"])
            revision["page"] = WikiPage(subreddit._reddit, subreddit, revision["page"], revision["id"])
            yield revision

    @cachedproperty
    def mod(self) -> WikiPageModeration:
        """Provide an instance of :class:`.WikiPageModeration`.

        For example, to add u/spez as an editor on the wikipage ``"praw_test"`` try:

        .. code-block:: python

            reddit.subreddit("test").wiki["praw_test"].mod.add("spez")

        """
        return WikiPageModeration(self)

    def __init__(
        self,
        reddit: praw.Reddit,
        subreddit: praw.models.Subreddit,
        name: str,
        revision: str | None = None,
        _data: dict[str, Any] | None = None,
    ) -> None:
        """Initialize a :class:`.WikiPage` instance.

        :param revision: A specific revision ID to fetch. By default, fetches the most
            recent revision.

        """
        self.name = name
        self._revision = revision
        self.subreddit = subreddit
        super().__init__(reddit, _data=_data, _str_field=False)

    def __repr__(self) -> str:
        """Return an object initialization representation of the instance."""
        return f"{self.__class__.__name__}(subreddit={self.subreddit!r}, name={self.name!r})"

    def __str__(self) -> str:
        """Return a string representation of the instance."""
        return f"{self.subreddit}/{self.name}"

    def _fetch(self) -> None:
        data = self._fetch_data()
        data = data["data"]
        if data["revision_by"] is not None:
            data["revision_by"] = Redditor(self._reddit, _data=data["revision_by"]["data"])
        self.__dict__.update(data)
        super()._fetch()

    def _fetch_info(self) -> tuple[str, dict[str, str], dict[str, str] | None]:
        return (
            "wiki_page",
            {"subreddit": self.subreddit, "page": self.name},
            {"v": self._revision} if self._revision else None,
        )

    def discussions(self, **generator_kwargs: Any) -> Iterator[praw.models.Submission]:
        """Return a :class:`.ListingGenerator` for discussions of a wiki page.

        Discussions are site-wide links to a wiki page.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        To view the titles of discussions of the page ``"praw_test"`` in r/test, try:

        .. code-block:: python

            for submission in reddit.subreddit("test").wiki["praw_test"].discussions():
                print(submission.title)

        """
        return ListingGenerator(
            self._reddit,
            API_PATH["wiki_discussions"].format(subreddit=self.subreddit, page=self.name),
            **generator_kwargs,
        )

    def edit(self, *, content: str, reason: str | None = None, **other_settings: Any) -> None:
        """Edit this wiki page's contents.

        :param content: The updated Markdown content of the page.
        :param reason: The reason for the revision.
        :param other_settings: Additional keyword arguments to pass.

        For example, to replace the first wiki page of r/test with the phrase ``"test
        wiki page"``:

        .. code-block:: python

            page = next(iter(reddit.subreddit("test").wiki))
            page.edit(content="test wiki page")

        """
        other_settings.update({"content": content, "page": self.name, "reason": reason})
        self._reddit.post(API_PATH["wiki_edit"].format(subreddit=self.subreddit), data=other_settings)

    def revision(self, revision: str) -> WikiPage:
        """Return a specific version of this page by revision ID.

        To view revision ``"1234abc"`` of ``"praw_test"`` in r/test:

        .. code-block:: python

            page = reddit.subreddit("test").wiki["praw_test"].revision("1234abc")

        """
        return WikiPage(self.subreddit._reddit, self.subreddit, self.name, revision)

    def revisions(self, **generator_kwargs: str | int | dict[str, str]) -> Iterator[WikiPage]:
        """Return a :class:`.ListingGenerator` for page revisions.

        Additional keyword arguments are passed in the initialization of
        :class:`.ListingGenerator`.

        To view the wiki revisions for ``"praw_test"`` in r/test try:

        .. code-block:: python

            for item in reddit.subreddit("test").wiki["praw_test"].revisions():
                print(item)

        To get :class:`.WikiPage` objects for each revision:

        .. code-block:: python

            for item in reddit.subreddit("test").wiki["praw_test"].revisions():
                print(item["page"])

        """
        url = API_PATH["wiki_page_revisions"].format(subreddit=self.subreddit, page=self.name)
        return self._revision_generator(generator_kwargs=generator_kwargs, subreddit=self.subreddit, url=url)
