import re

from loguru import logger
from requests import RequestException

from flexget import plugin
from flexget.entry import Entry
from flexget.event import event

from . import db

logger = logger.bind(name='next_trakt_episodes')


class NextTraktEpisodes:
    """
    Creates an entry for the latest or the next item in your watched or collected
    episodes in your trakt account.

    Syntax:

    next_trakt_episodes:
      account: <value>
      username: <value>
      position: <last|next>
      context: <collect|collected|watch|watched>
      list: <value>

    `account` option is required if the profile is private. `username` will default to account owner if `account` is
    specified.

    """

    schema = {
        'type': 'object',
        'properties': {
            'username': {'type': 'string'},
            'account': {'type': 'string'},
            'position': {'type': 'string', 'enum': ['last', 'next'], 'default': 'next'},
            'context': {
                'type': 'string',
                'enum': ['watched', 'collected', 'aired'],
                'default': 'watched',
            },
            'list': {'type': 'string'},
            'strip_dates': {'type': 'boolean', 'default': False},
        },
        'required': ['list'],
        'anyOf': [{'required': ['username']}, {'required': ['account']}],
        'error_anyOf': 'At least one of `username` or `account` options are needed.',
        'additionalProperties': False,
    }

    def on_task_input(self, task, config):
        if config.get('account') and not config.get('username'):
            config['username'] = 'me'
        session = db.get_session(account=config.get('account'))
        listed_series = {}
        args = ('users', config['username'])
        if config['list'] in ['collection', 'watchlist', 'watched']:
            args += (config['list'], 'shows')
        else:
            args += ('lists', db.make_list_slug(config['list']), 'items')
        try:
            data = session.get(db.get_api_url(args)).json()
        except RequestException as e:
            raise plugin.PluginError('Unable to get trakt list `{}`: {}'.format(config['list'], e))
        if not data:
            logger.warning('The list "{}" is empty.', config['list'])
            return
        for item in data:
            if item.get('show'):
                if not item['show']['title']:
                    # Seems we can get entries with a blank show title sometimes
                    logger.warning('Found trakt list show without series name.')
                    continue
                ids = item['show']['ids']
                trakt_id = ids['trakt']
                listed_series[trakt_id] = {
                    'series_name': '{} ({})'.format(item['show']['title'], item['show']['year']),
                    'trakt_id': trakt_id,
                    'trakt_series_name': item['show']['title'],
                    'trakt_series_year': item['show']['year'],
                    'trakt_list': config.get('list'),
                }
                for id_name, id_value in ids.items():
                    entry_field_name = (
                        'trakt_slug' if id_name == 'slug' else id_name
                    )  # rename slug to trakt_slug
                    listed_series[trakt_id][entry_field_name] = id_value
        context = 'collection' if config['context'] == 'collected' else config['context']
        entries = []
        for trakt_id, fields in listed_series.items():
            if context == 'aired':
                url = db.get_api_url('shows', trakt_id, '{}_episode'.format(config['position']))
            else:
                url = db.get_api_url('shows', trakt_id, 'progress', context)
            try:
                response = session.get(url)
                if response.status_code == 204:
                    logger.debug('No {} episode for {}', config['position'], fields['series_name'])
                    continue
                data = response.json()
            except RequestException as e:
                raise plugin.PluginError(
                    f'An error has occurred looking up: Trakt_id: {trakt_id} Error: {e}'
                )
            if context == 'aired':
                season_number = data['season']
                episode_number = data['number']
            elif config['position'] == 'next' and data.get('next_episode'):
                # If the next episode is already in the trakt database, we'll get it here
                season_number = data['next_episode']['season']
                episode_number = data['next_episode']['number']
            else:
                # If we need last ep, or next_episode was not provided, search for last ep
                for season in reversed(data['seasons']):
                    # Find the first season with collected/watched episodes
                    if not season['completed']:
                        continue
                    season_number = season['number']
                    # Pick the highest collected/watched episode
                    episode_number = max(
                        item['number'] for item in season['episodes'] if item['completed']
                    )
                    # If we are in next episode mode, we have to increment this number
                    if config['position'] == 'next':
                        if season['completed'] >= season['aired']:
                            # TODO: next_episode doesn't count unaired episodes right now, this will skip to next
                            # season too early when there are episodes left to air this season.
                            season_number += 1
                        episode_number = 1
                    break
                else:
                    if config['position'] != 'next':
                        # There were no watched/collected episodes, nothing to emit in 'last' mode
                        continue
                    season_number = episode_number = 1

            if season_number and episode_number:
                if config.get('strip_dates'):
                    # remove year from end of series_name if present
                    fields['series_name'] = re.sub(r'\s+\(\d{4}\)$', '', fields['series_name'])
                entry = self.make_entry(fields, season_number, episode_number)
                entries.append(entry)
        return entries

    @staticmethod
    def make_entry(fields, season, episode):
        entry = Entry()
        entry.update(fields)
        entry['series_season'] = season
        entry['series_episode'] = episode
        entry['series_id_type'] = 'ep'
        entry['series_id'] = 'S%02dE%02d' % (season, episode)
        entry['title'] = entry['series_name'] + ' ' + entry['series_id']
        entry['url'] = 'https://trakt.tv/shows/{}/seasons/{}/episodes/{}'.format(
            fields['trakt_id'],
            season,
            episode,
        )
        return entry


@event('plugin.register')
def register_plugin():
    plugin.register(NextTraktEpisodes, 'next_trakt_episodes', api_ver=2)
