"""ESPN league model."""

# pylint: disable=too-many-locals,too-many-arguments,line-too-long,too-many-branches,too-many-statements
import datetime
import logging
from typing import Any, Iterator
from urllib.parse import urlparse

import requests
import tqdm
from dateutil.parser import parse
from scrapesession.scrapesession import ScrapeSession  # type: ignore

from ..game_model import GameModel
from ..league import League
from ..league_model import SHUTDOWN_FLAG, LeagueModel, needs_shutdown
from ..season_type import SeasonType
from .espn_game_model import create_espn_game_model


def _season_type_from_name(name: str) -> SeasonType:
    if (
        name == "Regular Season"
        or "English Premier League" in name
        or "Group Stage" in name
        or "Spring Regular Season" in name
        or "Round of" in name
        or "Second Round" in name
        or "2015-2016 Barclays Premier League" in name
        or "2014-2015 Barclays Premier League" in name
        or "2013-2014 Barclays Premier League" in name
        or "2012-2013 Barclays Premier League" in name
        or "2011-2012 Barclays Premier League" in name
        or "2010-2011 Barclays Premier League" in name
        or "2009-2010 Barclays Premier League" in name
        or "2008-2009 Barclays Premier League" in name
        or "2007-2008 Barclays Premier League" in name
        or "2006-2007 Barclays Premier League" in name
        or "2005-2006 Barclays Premier League" in name
        or "2004-2005 Barclays Premier League" in name
        or "2003-2004 Barclays Premier League" in name
        or "2002-2003 Barclays Premier League" in name
        or "2001-2002 Barclays Premier League" in name
        or "2025-26 LALIGA" in name
        or "2025-26 German Bundesliga" in name
        or "2024-25 German Bundesliga" in name
    ):
        return SeasonType.REGULAR
    if name == "Preseason" or "Spring Training" in name:
        return SeasonType.PRESEASON
    if (
        name == "Postseason"
        or "Quarterfinals" in name
        or "Semifinals" in name
        or "3rd-Place" in name
        or "Final" in name
        or "Third Place" in name
        or "Quarter-finals" in name
        or "Semi-finals" in name
        or "Play-In Season" in name
        or "Spring Postseason" in name
        or "Qualifying Finals" in name
    ):
        return SeasonType.POSTSEASON
    if name == "Off Season":
        return SeasonType.OFFSEASON
    raise ValueError(f"Unrecognised season name: {name}")


class ESPNLeagueModel(LeagueModel):
    """ESPN implementation of the league model."""

    def __init__(
        self,
        start_url: str,
        league: League,
        session: ScrapeSession,
        position: int | None = None,
    ) -> None:
        super().__init__(league, session, position=position)
        self._start_url = start_url

    @classmethod
    def name(cls) -> str:
        return "espn-league-model"

    @classmethod
    def position_validator(cls) -> dict[str, str]:
        """A dictionary that contains the mapping from positions to standard positions."""
        raise NotImplementedError(
            "position_validator is not implemented by parent class"
        )

    def _produce_game(
        self,
        cache_disabled: bool,
        event_item: dict[str, Any],
        week_count: int | None,
        game_number: int,
        season_type_json: dict[str, Any],
        pbar: tqdm.tqdm,
    ) -> Iterator[GameModel]:
        event = event_item
        if "$ref" in event_item:
            if cache_disabled:
                with self.session.cache_disabled():
                    event_response = self.session.get(event_item["$ref"])
            else:
                event_response = self.session.get(event_item["$ref"])
            event_response.raise_for_status()
            event = event_response.json()

        competitions = event.get("competitions", [])
        if not competitions:
            for grouping in event.get("groupings", []):
                competitions.extend(grouping["competitions"])

        for competition in competitions:
            if "status" in competition:
                status_response = self.session.get(competition["status"]["$ref"])
                status_response.raise_for_status()
                status = status_response.json()
                if not status["type"]["completed"]:
                    continue
            game_model = create_espn_game_model(
                competition,
                week_count,
                game_number,
                self.session,
                self.league,
                season_type_json.get("year"),
                _season_type_from_name(season_type_json["name"]),
                self.position_validator(),
            )
            game_number += 1
            pbar.update(1)
            pbar.set_description(
                f"ESPN {game_model.year} - {game_model.season_type} - {game_model.dt}"
            )
            yield game_model

    def _produce_games(
        self,
        week: dict[str, Any],
        week_count: int,
        season_type_json: dict[str, Any],
        pbar: tqdm.tqdm,
        cache_disabled: bool,
    ) -> Iterator[GameModel]:
        events_page = 1
        events_count = 0
        while True:
            if "events" not in week:
                break
            events_response = self.session.get(
                week["events"]["$ref"] + f"&page={events_page}"
            )
            events_response.raise_for_status()
            events = events_response.json()
            for event_item in events["items"]:
                for game_model in self._produce_game(
                    event_item=event_item,
                    game_number=events_count,
                    week_count=week_count,
                    cache_disabled=cache_disabled,
                    season_type_json=season_type_json,
                    pbar=pbar,
                ):
                    events_count += 1
                    yield game_model
            if events_page >= events["pageCount"]:
                break
            events_page += 1
        qbr_page = 1
        qbr_count = 0
        while True:
            if "qbr" not in week:
                break
            qbr_response = self.session.get(week["qbr"]["$ref"] + f"&page={qbr_page}")
            qbr = qbr_response.json()
            for qbr_item in qbr["items"]:
                for game_model in self._produce_game(
                    event_item=qbr_item["event"],
                    game_number=qbr_count,
                    week_count=week_count,
                    cache_disabled=cache_disabled,
                    season_type_json=season_type_json,
                    pbar=pbar,
                ):
                    yield game_model
                    qbr_count += 1
            if qbr_page >= qbr["pageCount"]:
                break
            qbr_page += 1

    def _produce_week_games(
        self,
        season_type_json: dict[str, Any],
        page: int,
        pbar: tqdm.tqdm,
        cache_disabled: bool,
    ) -> Iterator[GameModel]:
        found_pages = False
        if "weeks" in season_type_json:
            game_page = 1
            week_count = 0
            while True:
                if cache_disabled:
                    with self.session.cache_disabled():
                        weeks_response = self.session.get(
                            season_type_json["weeks"]["$ref"] + f"&page={page}"
                        )
                else:
                    weeks_response = self.session.get(
                        season_type_json["weeks"]["$ref"] + f"&page={page}"
                    )
                weeks_response.raise_for_status()
                weeks = weeks_response.json()
                for item in weeks["items"]:
                    if needs_shutdown():
                        return
                    if cache_disabled:
                        with self.session.cache_disabled():
                            week_response = self.session.get(item["$ref"])
                    else:
                        week_response = self.session.get(item["$ref"])
                    week_response.raise_for_status()
                    week = week_response.json()
                    for game_model in self._produce_games(
                        week, week_count, season_type_json, pbar, cache_disabled
                    ):
                        found_pages = True
                        if game_model.dt.date() >= datetime.datetime.now().date():
                            continue
                        yield game_model
                    week_count += 1
                if game_page >= weeks["pageCount"]:
                    break
                game_page += 1
        if not found_pages and self.league != League.NFL:
            # Lets check the scoreboards via the calendar
            o = urlparse(season_type_json["$ref"])
            path_components = o.path.split("/")
            sport_slug = path_components[3]
            league_slug = path_components[5]
            start_date = parse(season_type_json["startDate"]).date()
            end_date = parse(season_type_json["endDate"]).date()
            calendar_dates = {
                (start_date + datetime.timedelta(days=x))
                for x in range((end_date - start_date).days)
            }
            events_count = 0
            while calendar_dates:
                calendar_date = calendar_dates.pop()
                if calendar_date > datetime.datetime.now().date() - datetime.timedelta(
                    days=2
                ):
                    continue
                dt = calendar_date.strftime("%Y%m%d")
                url = f"https://site.api.espn.com/apis/site/v2/sports/{sport_slug}/{league_slug}/scoreboard?lang=en&region=us&calendartype=whitelist&limit=100&dates={dt}&league={league_slug}"
                scoreboard_response = self.session.get(url)
                scoreboard_response.raise_for_status()
                try:
                    scoreboard = scoreboard_response.json()
                    calendar_list = scoreboard["leagues"][0]["calendar"]
                    if calendar_list:
                        if isinstance(calendar_list[0], str):
                            declared_calendar_dates = {
                                parse(x).date() for x in calendar_list
                            }
                            calendar_dates &= declared_calendar_dates
                    for event in scoreboard["events"]:
                        if not event:
                            continue
                        try:
                            event_id = event["id"]
                        except KeyError as exc:
                            logging.error(str(exc))
                            logging.error(event)
                            raise exc
                        event_response = self.session.get(
                            f"https://sports.core.api.espn.com/v2/sports/{sport_slug}/leagues/{league_slug}/events/{event_id}?lang=en&region=us"
                        )
                        event_response.raise_for_status()
                        for game_model in self._produce_game(
                            event_item=event_response.json(),
                            game_number=events_count,
                            season_type_json=season_type_json,
                            pbar=pbar,
                            cache_disabled=False,
                            week_count=None,
                        ):
                            yield game_model
                            events_count += 1
                except requests.exceptions.JSONDecodeError as exc:
                    logging.error(scoreboard_response.text)
                    logging.error(url)
                    logging.error(str(exc))

    @property
    def games(self) -> Iterator[GameModel]:
        try:
            with self.session.wayback_disabled():
                page = 1
                first = True
                with tqdm.tqdm(position=self.position) as pbar:
                    while True:
                        if page == 1:
                            with self.session.cache_disabled():
                                response = self.session.get(
                                    self._start_url + f"&page={page}"
                                )
                        else:
                            response = self.session.get(
                                self._start_url + f"&page={page}"
                            )
                        response.raise_for_status()
                        seasons = response.json()
                        for item in seasons.get("items", []):
                            season_response = self.session.get(item["$ref"])
                            season_response.raise_for_status()
                            season_json = season_response.json()

                            for season_item in season_json["types"]["items"]:
                                season_type_response = self.session.get(
                                    season_item["$ref"]
                                )
                                season_type_response.raise_for_status()
                                season_type_json = season_type_response.json()

                                yield from self._produce_week_games(
                                    season_type_json, page, pbar, first
                                )
                            first = False

                        if page >= seasons.get("pageCount", 0):
                            break
                        page += 1
        except Exception as exc:
            SHUTDOWN_FLAG.set()
            raise exc
