"""Timelines mixin: public, home, account, and hashtag timeline retrieval."""

import json
import logging
from typing import Any

from httpx import HTTPError

from minimal_activitypub import __display_name__
from minimal_activitypub.exceptions import NetworkError
from minimal_activitypub.types import ActivityPubClass
from minimal_activitypub.utils import _check_exception

logger = logging.getLogger(__display_name__)

# Type alias re-used here to match the rest of the codebase
Status = dict[str, Any]


class _TimelinesMixin:
    """Mixin providing timeline retrieval methods."""

    async def get_account_statuses(
        self: ActivityPubClass,
        account_id: str,
        max_id: str | None = None,
        min_id: str | None = None,
    ) -> list[Status]:
        """Get statuses of a given account.

        :param account_id: The account ID of the account you want to get the statuses of
        :param max_id: The ID of the last status you want to get
        :param min_id: The ID of the oldest status you want to retrieve

        :returns: A list of statuses.

        """
        logger.debug(
            "ActivityPub.get_account_statuses(account_id=%s, max_id=%s, min_id=%s)",
            account_id,
            max_id,
            min_id,
        )

        await self._pre_call_checks()

        paging = "?"
        url = f"{self.instance}/api/v1/accounts/{account_id}/statuses"
        if max_id:
            paging += f"max_id={max_id}"
        if min_id:
            if len(paging) > 1:
                paging += "&"
            paging += f"min_id={min_id}"
        if max_id or min_id:
            url += paging
        logger.debug("ActivityPub.get_account_statuses - url = %s", url)
        try:
            response = await self.client.get(url=url, headers=self.headers, timeout=self.timeout)
            self._update_ratelimit(response.headers)
            await _check_exception(response=response)
            self._parse_next_prev(links=response.headers.get("Link"))
            result: list[Status] = response.json()
        except HTTPError as error:
            raise NetworkError from error

        logger.debug(
            "ActivityPub.get_account_statuses -> result:\n%s",
            json.dumps(result, indent=4),
        )
        return result

    async def get_public_timeline(  # noqa: PLR0913 This method has this many arguments
        self: ActivityPubClass,
        local: bool = False,
        remote: bool = False,
        only_media: bool = False,
        max_id: str | None = None,
        since_id: str | None = None,
        min_id: str | None = None,
        limit: int = 20,
    ) -> list[Status]:
        """Get statuses of the public timeline.

        :param local: Show only local statuses? Defaults to False.
        :param remote: Show only remote statuses? Defaults to False.
        :param only_media: Show only statuses with media attached? Defaults to False.
        :param max_id: All results returned will be lesser than this ID. In effect, sets an upper bound on results.
        :param since_id: All results returned will be greater than this ID. In effect, sets a lower bound on results.
        :param min_id: Returns results immediately newer than this ID. In effect, sets a cursor at this ID and
            paginates forward.
        :param limit: Maximum number of results to return. Defaults to 20 statuses. Max 40 statuses.

        :returns: A list of statuses.

        """
        logger.debug(
            "ActivityPub.get_timeline(local=%s, remote=%s, only_media=%s, max_id=%s, since_id=%s, min_id=%s, limit=%s)",
            local,
            remote,
            only_media,
            max_id,
            since_id,
            min_id,
            limit,
        )

        await self._pre_call_checks()

        url = f"{self.instance}/api/v1/timelines/public"

        params: dict[str, Any] = {"limit": limit}
        if local:
            params["local"] = local
        if remote:
            params["remote"] = remote
        if only_media:
            params["only_media"] = only_media
        if max_id:
            params["max_id"] = max_id
        if min_id:
            params["min_id"] = min_id
        if since_id:
            params["since-id"] = since_id

        logger.debug("ActivityPub.get_public_timeline - url = %s", url)
        logger.debug("ActivityPub.get_public_timeline - params = %s", params)
        try:
            response = await self.client.get(url=url, headers=self.headers, params=params, timeout=self.timeout)
            self._update_ratelimit(response.headers)
            await _check_exception(response=response)
            self._parse_next_prev(links=response.headers.get("Link"))
            result: list[Status] = response.json()
        except HTTPError as error:
            raise NetworkError from error

        logger.debug(
            "ActivityPub.get_timeline -> result:\n%s",
            json.dumps(result, indent=4),
        )
        return result

    async def get_home_timeline(
        self: ActivityPubClass,
        max_id: str | None = None,
        since_id: str | None = None,
        min_id: str | None = None,
        limit: int = 20,
    ) -> list[Status]:
        """Get statuses of the home timeline.

        :param max_id: All results returned will be lesser than this ID. In effect, sets an upper bound on results.
        :param since_id: All results returned will be greater than this ID. In effect, sets a lower bound on results.
        :param min_id: Returns results immediately newer than this ID. In effect, sets a cursor at this ID and
            paginates forward.
        :param limit: Maximum number of results to return. Defaults to 20 statuses. Max 40 statuses.

        :returns: A list of statuses.

        """
        logger.debug(
            "ActivityPub.get_timeline(max_id=%s, since_id=%s, min_id=%s, limit=%s)",
            max_id,
            since_id,
            min_id,
            limit,
        )

        await self._pre_call_checks()

        url = f"{self.instance}/api/v1/timelines/home"

        params: dict[str, Any] = {"limit": limit}
        if max_id:
            params["max_id"] = max_id
        if min_id:
            params["min_id"] = min_id
        if since_id:
            params["since-id"] = since_id

        logger.debug("ActivityPub.get_home_timeline - url = %s", url)
        logger.debug("ActivityPub.get_home_timeline - params = %s", params)
        try:
            response = await self.client.get(url=url, headers=self.headers, params=params, timeout=self.timeout)
            self._update_ratelimit(response.headers)
            await _check_exception(response=response)
            self._parse_next_prev(links=response.headers.get("Link"))
            result: list[Status] = response.json()
        except HTTPError as error:
            raise NetworkError from error

        logger.debug(
            "ActivityPub.get_timeline -> result:\n%s",
            json.dumps(result, indent=4),
        )
        return result

    async def get_hashtag_timeline(  # noqa: PLR0913
        self: ActivityPubClass,
        hashtag: str,
        any_tags: list[str] | None = None,
        all_tags: list[str] | None = None,
        none_tags: list[str] | None = None,
        local: bool = False,
        remote: bool = False,
        only_media: bool = False,
        max_id: str | None = None,
        since_id: str | None = None,
        min_id: str | None = None,
        limit: int = 20,
    ) -> list[Status]:
        """Get timeline of statuses with hashtags.

        :param hashtag: Hashtag excluding "#" to list statuses for
        :param any_tags: Return statuses that also contain any of these additional tags.
        :param all_tags: Return statuses that also contain all of these additional tags.
        :param none_tags: Return statuses that contain none of these additional tags.
        :param local: Return only local statuses? Defaults to False.
        :param remote: Return only remote statuses? Defaults to False.
        :param only_media: Return only statuses with media attachments? Defaults to False.
        :param max_id: All results returned will be lesser than this ID. In effect, sets an upper bound on results.
        :param since_id: All results returned will be greater than this ID. In effect, sets a lower bound on results.
        :param min_id: Returns results immediately newer than this ID. In effect, sets a cursor at this ID and
            paginates forward.
        :param limit: Maximum number of results to return, per type. Defaults to 20 results per category.
            Max 40 results per category.
        """
        url = f"{self.instance}/api/v1/timelines/tag/:{hashtag}"

        params: dict[str, int | str | bool | list[Any]] = {
            "local": local,
            "remote": remote,
            "only_media": only_media,
            "limit": limit,
        }
        if any_tags:
            params["any[]"] = any_tags
        if all_tags:
            params["all[]"] = all_tags
        if none_tags:
            params["none[]"] = none_tags
        if max_id:
            params["max_id"] = max_id
        if since_id:
            params["since-id"] = since_id
        if min_id:
            params["min_id"] = min_id

        logger.debug("Url=%s", url)
        logger.debug("Headers=%s", self.headers)
        logger.debug("Params=%s", params)
        try:
            response = await self.client.get(
                url=url,
                headers=self.headers,
                params=params,
                timeout=self.timeout,
            )
            self._update_ratelimit(headers=response.headers)
            await _check_exception(response=response)
            result: list[Status] = response.json()
        except HTTPError as error:
            raise NetworkError from error

        logger.debug("ActivityPub.search -> result:\n%s", json.dumps(result, indent=4))

        return result
