"""Untappd beers."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from core.mixins import SimpleRepr
from dateutil.parser import parse as parse_date

from untappd_scraper.html_session import get
from untappd_scraper.structs.web import WebActivityBeer, WebBeerDetails
from untappd_scraper.web import id_from_href, parsed_value, slug_from_href

if TYPE_CHECKING:  # pragma: no cover
    from collections.abc import Iterator

    from requests_html import Element, HTMLResponse


class Beer(SimpleRepr):
    """Untappd beer."""

    def __init__(self, beer_id: int) -> None:
        """Initiate a Beer object, storing the beer ID and loading details.

        Raises:
            ValueError: invalid beer ID

        Args:
            beer_id (int): beer ID
        """
        self.beer_id = beer_id

        self._page = get(url_of(beer_id))
        if not self._page.ok:
            msg = f"Invalid beer ID {beer_id} ({self._page})"
            raise ValueError(msg)
        self._beer_details: WebBeerDetails = beer_details(resp=self._page)

    def __getattr__(self, name: str) -> Any:
        """Return unknown attributes from beer details.

        Args:
            name (str): attribute to lookup

        Returns:
            Any: attribute value
        """
        return getattr(self._beer_details, name)


# ----- utils -----


def url_of(beer_id: int) -> str:
    """Return the URL for a beer's main page.

    Args:
        beer_id (int): beer ID

    Returns:
        str: url to load to get beer's main page
    """
    return f"https://untappd.com/beer/{beer_id}"


# ----- beer details processing -----


def beer_details(resp: HTMLResponse) -> WebBeerDetails:
    """Parse a user's main page into user details.

    Args:
        resp (HTMLResponse): beer's main page loaded

    Returns:
        WebBeerDetails: general beer details
    """
    content_el = resp.html.find(".main .content", first=True)
    description = "".join(
        content_el.find(".desc .beer-descrption-read-less", first=True).xpath("//div/text()")
    ).strip()

    return WebBeerDetails(
        beer_id=id_from_href(content_el.find("a.check", first=True)),
        name=content_el.find(".name h1", first=True).text,
        description=description.strip(),
        brewery=content_el.find(".name .brewery a", first=True).text,
        brewery_slug=slug_from_href(content_el.find(".name .brewery a", first=True)),
        style=content_el.find(".name p.style", first=True).text,
        url=resp.url,
        global_rating=content_el.find(".details [data-rating]", first=True).attrs[
            "data-rating"
        ],
        num_ratings=parsed_value(
            "{:d} Rat", content_el.find(".details p.raters", first=True).text
        ),
    )


def checkin_activity(resp: HTMLResponse) -> Iterator[WebActivityBeer]:
    """Parse all available recent checkins for a user or in a venue.

    Args:
        resp (HTMLResponse): user's main page or venue's activity page

    Returns:
        Iterator[WebActivityBeer]: user's visible recent checkins
    """
    return (checkin_details(checkin) for checkin in resp.html.find(".activity .item"))


def checkin_details(checkin_item: Element) -> WebActivityBeer:
    """Extract beer details from a checkin.

    Args:
        checkin_item (Element): single checkin

    Returns:
        WebActivityBeer: Interesting details for a beer
    """
    user_el, beer_el, brewery_el, location_el = extract_checkin_elements(checkin_item)

    checkin_time_el = checkin_item.find(".bottom .time", first=True)
    checkin_time = checkin_time_el.attrs.get("data-gregtime", checkin_time_el.text)
    checkin_time = parse_date(checkin_time)
    assert checkin_time.tzinfo and checkin_time.tzinfo.utcoffset(checkin_time) is not None, (
        f"Naive datetime from {checkin_time_el.html=}"
    )

    purchased_at = checkin_item.find(".purchased a", first=True)
    try:
        comment = checkin_item.find("p.comment-text", first=True).text
    except AttributeError:
        comment = None
    serving = checkin_item.find(".serving", first=True)

    data_rating_element = checkin_item.find("[data-rating]", first=True)
    if data_rating_element:
        data_rating: float | None = float(data_rating_element.attrs["data-rating"])
    else:
        data_rating = None  # pragma: no cover

    try:
        friends: list[str] | None = [
            slug_from_href(href) for href in checkin_item.find(".tagged-friends a")
        ]
    except AttributeError:  # pragma: no cover
        friends = None

    return WebActivityBeer(
        checkin_id=int(checkin_item.attrs["data-checkin-id"]),
        checkin=checkin_time,
        user_name=slug_from_href(user_el),
        name=beer_el.text,
        beer_id=id_from_href(beer_el),
        brewery=brewery_el.text,
        brewery_slug=slug_from_href(brewery_el),
        location=location_el.text if location_el else None,
        location_id=id_from_href(location_el) if location_el else None,
        purchased_at=purchased_at.text if purchased_at else None,
        purchased_id=id_from_href(purchased_at) if purchased_at else None,
        comment=comment,
        serving=serving.text if serving else None,
        user_rating=data_rating,
        friends=friends,
    )


def extract_checkin_elements(
    element: Element,
) -> tuple[Element, Element, Element, Element | None]:
    """Extract four linked elements in a checkin.

    Args:
        element (Element): checkin element

    Raises:
        ValueError: element passed didn't contain 3-4 <a> tags

    Returns:
        tuple[Element, Element, Element, Element]: user, beer, brewery, location
    """
    elements = element.find(".top .text a")

    if len(elements) == 3:
        return elements + [None]  # pragma: no cover
    if len(elements) == 4:
        return elements

    raise ValueError(
        f"Wanted 3 or 4 <a> elements (not {len(elements)}) in {element.html}"
    )  # pragma: no cover
