from i3pystatus.core.util import internet, require
from i3pystatus.scores import ScoresBackend

import copy
import json
import pytz
import re
import time
from datetime import datetime
from urllib.request import urlopen

LIVE_URL = 'https://www.nhl.com/gamecenter/{id}'
SCOREBOARD_URL = 'https://www.nhl.com/scores'
API_URL = 'https://statsapi.web.nhl.com/api/v1/schedule?startDate={date:%Y-%m-%d}&endDate={date:%Y-%m-%d}&expand=schedule.teams,schedule.linescore,schedule.broadcasts.all&site=en_nhl&teamId='


class NHL(ScoresBackend):
    '''
    Backend to retrieve NHL scores. For usage examples, see :py:mod:`here
    <.scores>`.

    .. rubric:: Available formatters

    * `{home_team}` — Depending on the value of the ``team_format`` option,
      will contain either the home team's name, abbreviation, or city
    * `{home_score}` — Home team's current score
    * `{home_wins}` — Home team's number of wins
    * `{home_losses}` — Home team's number of losses
    * `{home_otl}` — Home team's number of overtime losses
    * `{home_favorite}` — Displays the value for the :py:mod:`.scores` module's
      ``favorite`` attribute, if the home team is one of the teams being
      followed. Otherwise, this formatter will be blank.
    * `{home_empty_net}` — Shows the value from the ``empty_net`` parameter
      when the home team's net is empty.
    * `{away_team}` — Depending on the value of the ``team_format`` option,
      will contain either the away team's name, abbreviation, or city
    * `{away_score}` — Away team's current score
    * `{away_wins}` — Away team's number of wins
    * `{away_losses}` — Away team's number of losses
    * `{away_otl}` — Away team's number of overtime losses
    * `{away_favorite}` — Displays the value for the :py:mod:`.scores` module's
      ``favorite`` attribute, if the away team is one of the teams being
      followed. Otherwise, this formatter will be blank.
    * `{away_empty_net}` — Shows the value from the ``empty_net`` parameter
      when the away team's net is empty.
    * `{period}` — Current period
    * `{venue}` — Name of arena where game is being played
    * `{start_time}` — Start time of game in system's localtime (supports
      strftime formatting, e.g. `{start_time:%I:%M %p}`)
    * `{overtime}` — If the game ended in overtime or a shootout, this
      formatter will show ``OT`` kor ``SO``. If the game ended in regulation,
      or has not yet completed, this formatter will be blank.

    .. rubric:: Playoffs

    In the playoffs, losses are not important (as the losses will be equal to
    the other team's wins). Therefore, it is a good idea during the playoffs to
    manually set format strings to exclude information on team losses. For
    example:

    .. code-block:: python

        from i3pystatus import Status
        from i3pystatus.scores import nhl

        status = Status()
        status.register(
            'scores',
            hints={'markup': 'pango'},
            colorize_teams=True,
            favorite_icon='<span size="small" color="#F5FF00">★</span>',
            backends=[
                nhl.NHL(
                    favorite_teams=['CHI'],
                    format='[{scroll} ]NHL: [{away_favorite} ]{away_team} ({away_wins}) at [{home_favorite} ]{home_team} ({home_wins}) {game_status}'
                ),
            ],
        )

    .. rubric:: Team abbreviations

    * **ANA** — Anaheim Ducks
    * **ARI** — Arizona Coyotes
    * **BOS** — Boston Bruins
    * **BUF** — Buffalo Sabres
    * **CAR** — Carolina Hurricanes
    * **CBJ** — Columbus Blue Jackets
    * **CGY** — Calgary Flames
    * **CHI** — Chicago Blackhawks
    * **COL** — Colorado Avalanche
    * **DAL** — Dallas Stars
    * **DET** — Detroit Red Wings
    * **EDM** — Edmonton Oilers
    * **FLA** — Florida Panthers
    * **LAK** — Los Angeles Kings
    * **MIN** — Minnesota Wild
    * **MTL** — Montreal Canadiens
    * **NJD** — New Jersey Devils
    * **NSH** — Nashville Predators
    * **NYI** — New York Islanders
    * **NYR** — New York Rangers
    * **OTT** — Ottawa Senators
    * **PHI** — Philadelphia Flyers
    * **PIT** — Pittsburgh Penguins
    * **SEA** — Seattle Kraken
    * **SJS** — San Jose Sharks
    * **STL** — St. Louis Blues
    * **TBL** — Tampa Bay Lightning
    * **TOR** — Toronto Maple Leafs
    * **VAN** — Vancouver Canucks
    * **VGK** — Vegas Golden Knights
    * **WPG** — Winnipeg Jets
    * **WSH** — Washington Capitals
    '''
    interval = 300

    settings = (
        ('favorite_teams', 'List of abbreviations of favorite teams. Games '
                           'for these teams will appear first in the scroll '
                           'list. A detailed description of how games are '
                           'ordered can be found '
                           ':ref:`here <scores-game-order>`.'),
        ('all_games', 'If set to ``True``, all games will be present in '
                      'the scroll list. If set to ``False``, then only '
                      'games from **favorite_teams** will be present in '
                      'the scroll list.'),
        ('display_order', 'When **all_games** is set to ``True``, this '
                          'option will dictate the order in which games from '
                          'teams not in **favorite_teams** are displayed'),
        ('format_no_games', 'Format used when no tracked games are scheduled '
                            'for the current day (does not support formatter '
                            'placeholders)'),
        ('format', 'Format used to display game information'),
        ('status_pregame', 'Format string used for the ``{game_status}`` '
                           'formatter when the game has not started '),
        ('status_in_progress', 'Format string used for the ``{game_status}`` '
                               'formatter when the game is in progress'),
        ('status_final', 'Format string used for the ``{game_status}`` '
                         'formatter when the game has finished'),
        ('status_postponed', 'Format string used for the ``{game_status}`` '
                             'formatter when the game has been postponed'),
        ('empty_net', 'Value for the ``{away_empty_net}`` or '
                      '``{home_empty_net}`` formatter when the net is empty. '
                      'When the net is not empty, these formatters will be '
                      'empty strings.'),
        ('team_colors', 'Dictionary mapping team abbreviations to hex color '
                        'codes. If overridden, the passed values will be '
                        'merged with the defaults, so it is not necessary to '
                        'define all teams if specifying this value.'),
        ('team_format', 'One of ``name``, ``abbreviation``, or ``city``. If '
                        'not specified, takes the value from the ``scores`` '
                        'module.'),
        ('date', 'Date for which to display game scores, in **YYYY-MM-DD** '
                 'format. If unspecified, the current day\'s games will be '
                 'displayed starting at 10am Eastern time, with last '
                 'evening\'s scores being shown before then. This option '
                 'exists primarily for troubleshooting purposes.'),
        ('live_url', 'URL string to launch NHL GameCenter. This value should '
                     'not need to be changed.'),
        ('scoreboard_url', 'Link to the NHL.com scoreboard page. Like '
                           '**live_url**, this value should not need to be '
                           'changed.'),
        ('api_url', 'Alternate URL string from which to retrieve score data. '
                    'Like **live_url**, this value should not need to be '
                    'changed.'),
    )

    required = ()

    _default_colors = {
        'ANA': '#B4A277',
        'ARI': '#AC313A',
        'BOS': '#F6BD27',
        'BUF': '#1568C5',
        'CAR': '#FA272E',
        'CBJ': '#1568C5',
        'CGY': '#D23429',
        'CHI': '#CD0E24',
        'COL': '#9F415B',
        'DAL': '#058158',
        'DET': '#E51937',
        'EDM': '#2F6093',
        'FLA': '#E51837',
        'LAK': '#DADADA',
        'MIN': '#176B49',
        'MTL': '#C8011D',
        'NJD': '#CC0000',
        'NSH': '#FDB71A',
        'NYI': '#F8630D',
        'NYR': '#1576CA',
        'OTT': '#C50B2F',
        'PHI': '#FF690B',
        'PIT': '#FFB81C',
        'SEA': '#96D8D8',
        'SJS': '#007888',
        'STL': '#1764AD',
        'TBL': '#296AD5',
        'TOR': '#296AD5',
        'VAN': '#0454FA',
        'VGK': '#B4975A',
        'WPG': '#1568C5',
        'WSH': '#E51937',
    }

    _valid_teams = [x for x in _default_colors]
    _valid_display_order = ['in_progress', 'final', 'pregame', 'postponed']

    display_order = _valid_display_order
    format_no_games = 'NHL: No games'
    format = '[{scroll} ]NHL: [{away_favorite} ]{away_team} [{away_score} ]({away_wins}-{away_losses}-{away_otl}) at [{home_favorite} ]{home_team} [{home_score} ]({home_wins}-{home_losses}-{home_otl}) {game_status}'
    status_pregame = '{start_time:%H:%M %Z}'
    status_in_progress = '({time_remaining} {period})'
    status_final = '(Final[/{overtime}])'
    status_postponed = 'PPD'
    empty_net = 'EN'
    team_colors = _default_colors
    live_url = LIVE_URL
    scoreboard_url = SCOREBOARD_URL
    api_url = API_URL

    # These will inherit from the Scores class if not overridden
    team_format = None

    @require(internet)
    def check_scores(self):
        self.get_api_date()
        url = self.api_url.format(date=self.date)

        game_list = self.get_nested(self.api_request(url),
                                    'dates:0:games',
                                    default=[])

        # Convert list of games to dictionary for easy reference later on
        data = {}
        team_game_map = {}
        for game in game_list:
            try:
                id_ = game['gamePk']
            except KeyError:
                continue

            try:
                for key in ('home', 'away'):
                    team = game['teams'][key]['team']['abbreviation'].upper()
                    if team in self.favorite_teams:
                        team_game_map.setdefault(team, []).append(id_)
            except KeyError:
                continue

            data[id_] = game

        self.interpret_api_return(data, team_game_map)

    def process_game(self, game):
        ret = {}

        self.logger.debug(f'Processing {self.name} game data: {game}')

        linescore = self.get_nested(game, 'linescore', default={})

        ret['id'] = game['gamePk']
        ret['live_url'] = self.live_url.format(id=ret['id'])
        ret['period'] = self.get_nested(
            linescore,
            'currentPeriodOrdinal')
        ret['time_remaining'] = self.get_nested(
            linescore,
            'currentPeriodTimeRemaining',
            callback=lambda x: x.capitalize())
        ret['venue'] = self.get_nested(
            game,
            'venue:name')

        pp_strength = self.get_nested(linescore, 'powerPlayStrength')

        for team in ('away', 'home'):
            team_data = self.get_nested(game, f'teams:{team}', default={})

            if team == 'home':
                ret['venue'] = self.get_nested(team_data, 'venue:name')

            ret[f'{team}_score'] = self.get_nested(
                team_data,
                'score',
                callback=self.zero_fallback,
                default=0)
            ret[f'{team}_wins'] = int(
                self.get_nested(
                    team_data,
                    'leagueRecord:wins',
                    callback=self.zero_fallback,
                    default=0,
                )
            )
            ret[f'{team}_losses'] = int(
                self.get_nested(
                    team_data,
                    'leagueRecord:losses',
                    callback=self.zero_fallback,
                    default=0,
                )
            )
            ret[f'{team}_otl'] = int(
                self.get_nested(
                    team_data,
                    'leagueRecord:ot',
                    callback=self.zero_fallback,
                    default=0,
                )
            )

            ret[f'{team}_city'] = self.get_nested(
                team_data,
                'team:shortName')
            ret[f'{team}_name'] = self.get_nested(
                team_data,
                'team:teamName')
            ret[f'{team}_abbreviation'] = self.get_nested(
                team_data,
                'team:abbreviation')
            ret[f'{team}_power_play'] = self.get_nested(
                linescore,
                f'teams:{team}:powerPlay',
                callback=lambda x: pp_strength if x and pp_strength != 'Even' else '')
            ret[f'{team}_empty_net'] = self.get_nested(
                linescore,
                f'teams:{team}:goaliePulled',
                callback=lambda x: self.empty_net if x else '')

        if game.get('gameType') == 'P':
            # Calculate wins/losses in current playoff series
            home_rem = ret['home_wins'] % 4
            away_rem = ret['away_wins'] % 4

            if ret['home_wins'] == ret['away_wins']:
                if home_rem == 0:
                    # Both teams have multiples of 4 wins, so series has no
                    # completed games.
                    ret['home_wins'] = ret['away_wins'] = 0
                else:
                    ret['home_wins'] = home_rem
                    ret['away_wins'] = away_rem
            elif ret['home_wins'] > ret['away_wins']:
                ret['home_wins'] = 4 if home_rem == 0 else home_rem
                ret['away_wins'] = away_rem
            else:
                ret['away_wins'] = 4 if away_rem == 0 else away_rem
                ret['home_wins'] = home_rem

            # Series losses are the other team's wins
            ret['home_losses'] = ret['away_wins']
            ret['away_losses'] = ret['home_wins']

        if self.get_nested(game, 'status:detailedState').lower() == 'postponed':
            ret['status'] = 'postponed'
        else:
            ret['status'] = self.get_nested(
                game,
                'status:abstractGameState',
                callback=lambda x: x.lower().replace(' ', '_'))

            if ret['status'] == 'live':
                ret['status'] = 'in_progress'
            elif ret['status'] == 'final':
                ret['overtime'] = self.get_nested(
                    linescore,
                    'currentPeriodOrdinal',
                    callback=lambda x: x if 'OT' in x or x == 'SO' else '')
            elif ret['status'] != 'in_progress':
                ret['status'] = 'pregame'

        # Game time is in UTC, ISO format, thank the FSM
        # Ex. 2016-04-02T17:00:00Z
        game_time_str = game.get('gameDate', '')
        try:
            game_time = datetime.strptime(game_time_str, '%Y-%m-%dT%H:%M:%SZ')
        except ValueError as exc:
            # Log when the date retrieved from the API return doesn't match the
            # expected format (to help troubleshoot API changes), and set an
            # actual datetime so format strings work as expected. The times
            # will all be wrong, but the logging here will help us make the
            # necessary changes to adapt to any API changes.
            self.logger.exception(
                f'Error encountered determining {self.name} game time for '
                f'game {game["id"]}'
            )
            game_time = datetime.datetime(1970, 1, 1)

        ret['start_time'] = pytz.utc.localize(game_time).astimezone()

        self.logger.debug(f'Returned {self.name} formatter data: {ret}')

        return ret
