#
# This file is part of SickGear.
#
# SickGear is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SickGear is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SickGear.  If not, see <http://www.gnu.org/licenses/>.
#
# Created on Sep 20, 2012
# @author: Dermot Buckley <dermot@buckley.ie>
# @copyright: Dermot Buckley
#

import traceback
from sqlite3 import Row

from exceptions_helper import ex

import sickgear
from . import db, logger
from .helpers import try_int
from .sgdatetime import SGDatetime

# noinspection PyUnreachableCode
if False:
    from typing import Dict, List, Optional, Tuple, Union
    from six import integer_types


def get_scene_numbering(tvid, prodid, season, episode, fallback_to_xem=True, show_obj=None, **kwargs):
    """
    Returns a tuple, (season, episode), with the scene numbering (if there is one),
    otherwise returns the xem numbering (if fallback_to_xem is set), otherwise
    returns the TVDB numbering.
    (so the return values will always be set)

    kwargs['scene_result']: type: Optional[List[Row]] passed through
    kwargs['show_result']: type: Optional[List[Row]] passed through

    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :param season:
    :type season: int
    :param episode:
    :type episode: int
    :param fallback_to_xem: If set (the default), check xem for matches if there is no local scene numbering
    :type fallback_to_xem: bool
    :param show_obj:
    :type show_obj:
    :return: (int, int) a tuple with (season, episode)
    :rtype: Tuple[int, int]
    """
    if None is prodid or None is season or None is episode:
        return season, episode

    tvid, prodid = int(tvid), int(prodid)
    if None is show_obj:
        show_obj = sickgear.helpers.find_show_by_id({tvid: prodid})
    if show_obj and not show_obj.is_scene:
        return season, episode

    result = find_scene_numbering(tvid, prodid, season, episode, scene_result=kwargs.get('scene_result'))
    if result:
        return result
    else:
        if fallback_to_xem:
            xem_result = find_xem_numbering(tvid, prodid, season, episode, show_result=kwargs.get('show_result'))
            if xem_result:
                return xem_result
        return season, episode


def find_scene_numbering(tvid, prodid, season, episode, scene_result=None):
    """
    Same as get_scene_numbering(), but returns None if scene numbering is not set
    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :param season: season number
    :type season: int
    :param episode: episode number
    :type episode: int
    :param scene_result:
    :type scene_result:
    :return:
    :rtype: Tuple[int, int] or None
    """
    if None is prodid or None is season or None is episode:
        return

    tvid, prodid = int(tvid), int(prodid)

    sql_result = None
    if None is not scene_result:
        for cur_row in scene_result:
            if cur_row['season'] == season and cur_row['episode'] == episode:
                if cur_row['scene_season'] or cur_row['scene_episode']:
                    sql_result = [cur_row]
                break
    else:
        my_db = db.DBConnection()
        sql_result = my_db.select(
            """
            SELECT scene_season, scene_episode
            FROM scene_numbering
            WHERE indexer = ? AND indexer_id = ? AND season = ? AND episode = ? AND (scene_season OR scene_episode) != 0
            """, [tvid, prodid, season, episode])

    if sql_result:
        s_s, s_e = try_int(sql_result[0]['scene_season'], None), try_int(sql_result[0]['scene_episode'], None)
        if None is not s_s and None is not s_e:
            return s_s, s_e


def get_scene_absolute_numbering(tvid, prodid, absolute_number, season, episode, fallback_to_xem=True,
                                 show_obj=None, **kwargs):
    """
    Returns a tuple, (season, episode), with the scene numbering (if there is one),
    otherwise returns the xem numbering (if fallback_to_xem is set), otherwise
    returns the TVDB numbering.
    (so the return values will always be set)

    kwargs['scene_result']: type: Optional[List[Row]] passed through
    kwargs['show_result']: type: Optional[List[Row]] passed through

    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :param absolute_number: absolute number
    :type absolute_number: int
    :param season: season number
    :type season: int
    :param episode: episode number
    :type episode: int
    :param fallback_to_xem: fallback_to_xem: bool If set (the default), check xem for matches if there is no
    local scene numbering
    :type fallback_to_xem: bool
    :param show_obj:
    :type show_obj:
    :return: (int, int) a tuple with (season, episode)
    :rtype: Tuple[int, int] or None
    """
    has_sxe = None is not season and None is not episode
    if None is prodid or (None is absolute_number and not has_sxe):
        return absolute_number

    tvid, prodid = int(tvid), int(prodid)

    if None is show_obj:
        show_obj = sickgear.helpers.find_show_by_id({tvid: prodid})
    if show_obj and not show_obj.is_scene and not has_sxe:
        return absolute_number

    result = find_scene_absolute_numbering(tvid, prodid, absolute_number, season, episode,
                                           scene_result=kwargs.get('scene_result'))
    if result:
        return result

    if fallback_to_xem:
        xem_result = find_xem_absolute_numbering(tvid, prodid, absolute_number, season, episode,
                                                 show_result=kwargs.get('show_result'))
        if xem_result:
            return xem_result
    return absolute_number


def find_scene_absolute_numbering(tvid, prodid, absolute_number, season=None, episode=None, scene_result=None):
    """
    Same as get_scene_numbering(), but returns None if scene numbering is not set

    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int or long
    :param absolute_number:
    :type absolute_number: int
    :param season:
    :type season: int
    :param episode:
    :type episode: int
    :param scene_result:
    :type scene_result:
    :return:
    :rtype: None or int
    """
    has_sxe = None is not season and None is not episode
    if None is prodid or (None is absolute_number and not has_sxe):
        return

    tvid, prodid = int(tvid), int(prodid)

    sql_result = None
    if None is not scene_result:
        for cur_row in scene_result:
            if cur_row['season'] == season and cur_row['episode'] == episode:
                if cur_row['scene_absolute_number']:
                    sql_result = [cur_row]
                break
    else:
        my_db = db.DBConnection()
        sql_vars, cond = (([absolute_number], 'absolute_number = ?'),
                          ([season, episode], 'season = ? AND episode = ?'))[has_sxe]
        sql_result = my_db.select(
            """
            SELECT scene_absolute_number
            FROM scene_numbering
            WHERE indexer = ? AND indexer_id = ? AND %s AND scene_absolute_number != 0
            """ % cond, [tvid, prodid] + sql_vars)

    if sql_result:
        return try_int(sql_result[0]['scene_absolute_number'], None)


def get_indexer_numbering(tvid, prodid, scene_season, scene_episode, fallback_to_xem=True, return_multiple=False):
    # type: (int, integer_types, int, int, bool, bool) -> Union[Tuple[Optional[int], Optional[int]], List[Tuple[Optional[int], Optional[int]]]]
    """

    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :param scene_season: scene season
    :type scene_season: int
    :param scene_episode: scene episode
    :type scene_episode: int
    :param fallback_to_xem:
    :type fallback_to_xem: bool
    :param return_multiple:
    :return: a tuple, (season, episode) with the TVDB numbering for (sceneSeason, sceneEpisode)
    (this works like the reverse of get_scene_numbering)
    :rtype: Tuple[int, int]
    """
    if None is prodid or None is scene_season or None is scene_episode:
        return scene_season, scene_episode

    tvid, prodid = int(tvid), int(prodid)

    my_db = db.DBConnection()
    sql_result = my_db.select(
        """
        SELECT season, episode
        FROM scene_numbering
        WHERE indexer = ? AND indexer_id = ? AND scene_season = ? AND scene_episode = ?
        """, [tvid, prodid, scene_season, scene_episode])

    if sql_result:
        if return_multiple and 1 < len(sql_result):
            return [(try_int(_s['season'], None), try_int(_s['episode'], None)) for _s in sql_result]
        ss, se = try_int(sql_result[0]['season'], None), try_int(sql_result[0]['episode'], None)
        if None is not ss and None is not se:
            return ss, se
    if fallback_to_xem:
        return get_indexer_numbering_for_xem(tvid, prodid, scene_season, scene_episode, return_multiple=return_multiple)
    return scene_season, scene_episode


def get_indexer_absolute_numbering(tvid, prodid, scene_absolute_number, fallback_to_xem=True, scene_season=None):
    """
    Returns a tuple, (season, episode, absolute_number) with the TVDB numbering for (sceneAbsoluteNumber)
    (this works like the reverse of get_absolute_numbering)
    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int or long
    :param scene_absolute_number:
    :type scene_absolute_number: int
    :param fallback_to_xem:
    :type fallback_to_xem: bool
    :param scene_season:
    :type scene_season: int
    :return:
    :rtype: int
    """
    if None is prodid or None is scene_absolute_number:
        return scene_absolute_number

    tvid, prodid = int(tvid), int(prodid)

    my_db = db.DBConnection()

    sql = """
    SELECT absolute_number
    FROM scene_numbering
    WHERE indexer = ? AND indexer_id = ? AND scene_absolute_number = ?
    """
    params = [tvid, prodid, scene_absolute_number]
    if None is not scene_season:
        sql += ' AND scene_season = ?'
        params += [scene_season]

    for cur_row in (my_db.select(sql, params) or []):
        an = try_int(cur_row['absolute_number'], None)
        if None is not an:
            return an
    if fallback_to_xem:
        return get_indexer_absolute_numbering_for_xem(tvid, prodid, scene_absolute_number, scene_season)
    return scene_absolute_number


def set_scene_numbering(tvid=None, prodid=None, season=None, episode=None, absolute_number=None,
                        scene_season=None, scene_episode=None, scene_absolute=None, anime=False):
    """
    Set scene numbering for a season/episode.
    To clear the scene numbering, leave both sceneSeason and sceneEpisode as None.

    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int
    :param season:
    :type season: int
    :param episode:
    :type episode: int
    :param absolute_number:
    :type absolute_number: int
    :param scene_season:
    :type scene_season: int
    :param scene_episode:
    :type scene_episode: int
    :param scene_absolute:
    :type scene_absolute: int
    :param anime:
    :type anime: bool
    """
    if None is tvid or None is prodid:
        return

    my_db = db.DBConnection()
    if None is not season and None is not episode:
        my_db.action(
            """
            INSERT OR IGNORE INTO scene_numbering
            (indexer, indexer_id, season, episode) VALUES (?,?,?,?)
            """, [tvid, prodid, season, episode])

        # sxe replaced abs_num as key, migrate data with only abs
        _, _, ep_absolute_number = _get_sea(tvid, prodid, season, episode)
        sql_result = my_db.select(
            """
            SELECT scene_season, scene_episode, scene_absolute_number
            FROM scene_numbering
            WHERE indexer = ? AND indexer_id = ? AND season IS NULL AND episode IS NULL AND absolute_number = ?
            """, [tvid, prodid, ep_absolute_number])

        if not len(sql_result):
            update, values = (('scene_absolute_number = ?', [scene_absolute]),
                              ('scene_season = ?, scene_episode = ?', [scene_season, scene_episode]))[not anime]
        else:
            for cur_row in sql_result:
                scene_season = scene_season or cur_row['scene_season']
                scene_episode = scene_episode or cur_row['scene_episode']
                scene_absolute = scene_absolute or cur_row['scene_absolute_number']

            update, values = ('scene_season = ?, scene_episode = ?, scene_absolute_number = ?',
                              [scene_season, scene_episode, scene_absolute])
        my_db.action(
            """
            UPDATE scene_numbering
            SET %s
            WHERE indexer = ? AND indexer_id = ?
            AND season = ? AND episode = ?
            """ % update, values + [tvid, prodid, season, episode])

        my_db.action(
            """
            DELETE
            FROM scene_numbering
            WHERE indexer = ? AND indexer_id = ? 
            AND ((absolute_number = ? OR (season = ? AND episode = ?))
                AND scene_season IS NULL AND scene_episode IS NULL AND scene_absolute_number IS NULL)
            """, [tvid, prodid, ep_absolute_number, season, episode])

    elif absolute_number:
        my_db.action(
            """
            INSERT OR IGNORE INTO scene_numbering
            (indexer, indexer_id, absolute_number) VALUES (?,?,?)
            """, [tvid, prodid, absolute_number])

        my_db.action(
            """
            UPDATE scene_numbering
            SET scene_absolute_number = ?
            WHERE indexer = ? AND indexer_id = ? AND absolute_number = ?
            """, [scene_absolute, tvid, prodid, absolute_number])


def find_xem_numbering(tvid, prodid, season, episode, show_result=None):
    """
    Returns the scene numbering, as retrieved from xem.
    Refreshes/Loads as needed.

    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int
    :param season:
    :type season: int
    :param episode:
    :type episode: int
    :param show_result:
    :type show_result:
    :return:
    :rtype: (int, int) a tuple of scene_season, scene_episode, or None if there is no special mapping.
    """
    if None is prodid or None is season or None is episode:
        return season, episode

    tvid, prodid = int(tvid), int(prodid)

    xem_refresh(tvid, prodid)

    sql_result = None
    if None is not show_result:
        if isinstance(show_result, Row) and (season, episode) == (show_result['season'], show_result['episode']) \
                and (show_result['scene_season'] or show_result['scene_episode']):
            sql_result = [show_result]
    else:
        my_db = db.DBConnection()
        sql_result = my_db.select(
            """
            SELECT scene_season, scene_episode
            FROM tv_episodes
            WHERE indexer = ? AND showid = ? AND season = ? AND episode = ? AND (scene_season OR scene_episode) != 0
            """, [tvid, prodid, season, episode])

    if sql_result:
        s_s, s_e = try_int(sql_result[0]['scene_season'], None), try_int(sql_result[0]['scene_episode'], None)
        if None is not s_s and None is not s_e:
            return s_s, s_e


def find_xem_absolute_numbering(tvid, prodid, absolute_number, season, episode, show_result=None):
    """
    Returns the scene numbering, as retrieved from xem.
    Refreshes/Loads as needed.

    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int
    :param absolute_number:
    :type absolute_number: int
    :param season:
    :type season: int
    :param episode:
    :type episode: int
    :param show_result:
    :type show_result:
    :return:
    :rtype: int
    """
    if None is prodid or None is absolute_number:
        return absolute_number

    tvid, prodid = int(tvid), int(prodid)

    xem_refresh(tvid, prodid)

    sql_result = None
    if None is not show_result:
        if isinstance(show_result, Row) and (season, episode) == (show_result['season'], show_result['episode']) \
                and show_result['scene_absolute_number']:
            sql_result = [show_result]
    else:
        my_db = db.DBConnection()
        sql_result = my_db.select(
            """
            SELECT scene_absolute_number
            FROM tv_episodes
            WHERE indexer = ? AND showid = ? AND season = ? AND episode = ? AND scene_absolute_number != 0
            """, [tvid, prodid, season, episode])

    if sql_result:
        return try_int(sql_result[0]['scene_absolute_number'], None)


def get_indexer_numbering_for_xem(tvid, prodid, scene_season, scene_episode, return_multiple=False):
    # type: (int, int, int, int, bool) -> Union[Tuple[Optional[int], Optional[int]], List[Tuple[Optional[int], Optional[int]]]]
    """
    Reverse of find_xem_numbering: lookup a tvdb season and episode using scene numbering

    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int
    :param scene_season:
    :type scene_season: int
    :param scene_episode:
    :type scene_episode: int
    :param return_multiple:
    :return:
    :rtype: (int, int) a tuple of (season, episode)
    """
    if None is prodid or None is scene_season or None is scene_episode:
        return scene_season, scene_episode

    tvid, prodid = int(tvid), int(prodid)

    xem_refresh(tvid, prodid)

    my_db = db.DBConnection()
    sql_result = my_db.select(
        """
        SELECT season, episode
        FROM tv_episodes
        WHERE indexer = ? AND showid = ? AND scene_season = ? AND scene_episode = ?
        """, [tvid, prodid, scene_season, scene_episode])

    if return_multiple and 1 < len(sql_result or []):
        return [(try_int(_s['season'], None), try_int(_s['episode'], None)) for _s in sql_result]

    for cur_row in (sql_result or []):
        ss, se = try_int(cur_row['season'], None), try_int(cur_row['episode'], None)
        if None is not ss and None is not se:
            return ss, se
        break

    return scene_season, scene_episode


def get_indexer_absolute_numbering_for_xem(tvid, prodid, scene_absolute_number, scene_season=None):
    """
    Reverse of find_xem_numbering: lookup a tvdb season and episode using scene numbering

    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int
    :param scene_absolute_number:
    :type scene_absolute_number: int
    :param scene_season:
    :type scene_season: int
    :return:
    :rtype: int
    """
    if None is prodid or None is scene_absolute_number:
        return scene_absolute_number

    tvid, prodid = int(tvid), int(prodid)

    xem_refresh(tvid, prodid)

    my_db = db.DBConnection()
    sql = """
    SELECT absolute_number
    FROM tv_episodes
    WHERE indexer = ? AND showid = ? AND scene_absolute_number = ?
    """
    params = [tvid, prodid, scene_absolute_number]
    if None is not scene_season:
        sql += ' AND scene_season = ?'
        params += [scene_season]

    for cur_row in (my_db.select(sql, params) or []):
        an = try_int(cur_row['absolute_number'], None)
        if None is not an:
            return an
        break

    return scene_absolute_number


def get_scene_numbering_for_show(tvid, prodid):
    """
    Returns a dict of (season, episode) : (scene_season, scene_episode) mappings
    for an entire show.  Both the keys and values of the dict are tuples.
    Will be empty if no scene numbers are set
    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: ing or long
    :return:
    :rtype: Dict
    """
    return _get_numbering_for_show('scene_numbering', tvid, prodid)


def has_xem_scene_mapping(tvid, prodid):
    """
    Test if a scene mapping exists for a show at XEM

    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int
    :return: True if scene mapping exists, False if not
    :rtype: Bool
    """
    return bool(get_xem_numbering_for_show(tvid, prodid))


def get_xem_numbering_for_show(tvid, prodid):
    """
    Returns a dict of (season, episode) : (scene_season, scene_episode) mappings
    for an entire show.  Both the keys and values of the dict are tuples.
    Will be empty if no scene numbers are set in xem
    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :return:
    :rtype: Dict
    """
    return _get_numbering_for_show('tv_episodes', tvid, prodid)


def _get_numbering_for_show(tbl, tvid, prodid):
    """

    :param tbl: table
    :type tbl: AnyStr
    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :return:
    :rtype: Dict
    """
    result = {}

    if None is not prodid:
        if 'tv_episodes' == tbl:
            xem_refresh(tvid, prodid)

        my_db = db.DBConnection()
        # noinspection SqlResolve
        sql_result = my_db.select(
            """
            SELECT season, episode, scene_season, scene_episode
            FROM %s
            WHERE indexer = ? AND %s = ? AND (scene_season OR scene_episode) != 0
            ORDER BY season, episode
            """ % (tbl, ('indexer_id', 'showid')['tv_episodes' == tbl]), [int(tvid), int(prodid)])

        for cur_row in sql_result:
            season, episode = try_int(cur_row['season'], None), try_int(cur_row['episode'], None)
            if None is not season and None is not episode:
                scene_season, scene_episode = try_int(cur_row['scene_season'], None), \
                                              try_int(cur_row['scene_episode'], None)
                if None is not scene_season and None is not scene_episode:
                    result[(season, episode)] = (scene_season, scene_episode)

    return result


def get_scene_absolute_numbering_for_show(tvid, prodid):
    """
    Returns a dict of (season, episode) : scene_absolute_number mappings for an entire show.
    Will be empty if no scene numbers are set
    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :return:
    :rtype: Dict
    """
    return _get_absolute_numbering_for_show('scene_numbering', tvid, prodid)


def get_xem_absolute_numbering_for_show(tvid, prodid):
    """
    Returns a dict of (season, episode) : scene_absolute_number mappings for an entire show.
    Will be empty if no scene numbers are set in xem
    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :return:
    :rtype: Dict
    """
    return _get_absolute_numbering_for_show('tv_episodes', tvid, prodid)


def _get_absolute_numbering_for_show(tbl, tvid, prodid):
    """

    :param tbl: table name
    :type tbl: AnyStr
    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :return:
    :rtype: Dict
    """
    result = {}

    if None is not prodid:
        if 'tv_episodes' == tbl:
            xem_refresh(tvid, prodid)

        my_db = db.DBConnection()
        # noinspection SqlResolve
        sql_result = my_db.select(
            """
            SELECT season, episode, absolute_number, scene_absolute_number
            FROM %s
            WHERE indexer = ? AND %s = ? AND scene_absolute_number != 0
            ORDER BY season, episode
            """ % (tbl, ('indexer_id', 'showid')['tv_episodes' == tbl]), [int(tvid), int(prodid)])

        for cur_row in sql_result:
            season, episode, abs_num = list(map(lambda x: try_int(cur_row[x], None),
                                                ('season', 'episode', 'absolute_number')))
            if None is season and None is episode and None is not abs_num:
                season, episode, _ = _get_sea(tvid, prodid, absolute_number=abs_num)

            if None is not season and None is not episode:
                scene_absolute_number = try_int(cur_row['scene_absolute_number'], None)
                if None is not scene_absolute_number:
                    result[(season, episode)] = scene_absolute_number

    return result


def _get_sea(tvid, prodid, season=None, episode=None, absolute_number=None):
    """

    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :param season: season number
    :type season: int or None
    :param episode: episode number
    :type episode: int or None
    :param absolute_number: absolute number
    :type absolute_number: int or None
    :return:
    :rtype: Tuple[int, int, int]
    """
    show_obj = sickgear.helpers.find_show_by_id({tvid: prodid}, no_mapped_ids=True)
    if show_obj:
        ep_obj = None
        if None is not absolute_number:
            ep_obj = show_obj.get_episode(absolute_number=absolute_number)
        elif None is not season and None is not episode:
            ep_obj = show_obj.get_episode(season, episode)
        if None is not ep_obj:
            season, episode, absolute_number = ep_obj.season, ep_obj.episode, ep_obj.absolute_number
    return season, episode, absolute_number


def xem_refresh(tvid, prodid, force=False):
    """
    Refresh data from xem for a tv show

    :param tvid:
    :type tvid: int
    :param prodid:
    :type prodid: int
    :param force:
    :type force: bool
    """
    if None is prodid:
        return

    tvid, prodid = int(tvid), int(prodid)
    tvinfo = sickgear.TVInfoAPI(tvid)

    if 'xem_origin' not in tvinfo.config \
            or prodid not in sickgear.scene_exceptions.MEMCACHE['release_map_xem'].get(tvid, []):
        return

    xem_origin = tvinfo.config['xem_origin']

    # XEM API URL
    # noinspection HttpUrlsUsage
    url = 'http://thexem.info/map/all?id=%s&origin=%s&destination=scene' % (prodid, xem_origin)

    max_refresh_age_secs = 86400  # 1 day

    my_db = db.DBConnection()
    sql_result = my_db.select(
        """
        SELECT last_refreshed
        FROM xem_refresh
        WHERE indexer = ? AND indexer_id = ?
        """, [tvid, prodid])
    if sql_result:
        last_refresh = int(sql_result[0]['last_refreshed'])
        refresh = SGDatetime.timestamp_near() > last_refresh + max_refresh_age_secs
    else:
        refresh = True

    if refresh or force:
        logger.debug(f'Looking up XEM scene mapping for show {prodid} on {tvinfo.name}')

        # mark refreshed
        my_db.upsert('xem_refresh',
                     dict(last_refreshed=SGDatetime.timestamp_near()),
                     dict(indexer=tvid, indexer_id=prodid))

        try:
            parsed_json = sickgear.helpers.get_url(url, parse_json=True, timeout=90)
            if not parsed_json or '' == parsed_json:
                logger.log(f'No XEM data for show {prodid} on {tvinfo.name}', logger.MESSAGE)
                return

            if 'success' in parsed_json['result']:
                cl = list(map(lambda entry: [
                        """
                        UPDATE tv_episodes
                        SET scene_season = ?, scene_episode = ?, scene_absolute_number = ?
                        WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?
                        """, [entry.get('scene%s' % ('', '_2')['scene_2' in entry]).get(v)
                              for v in ('season', 'episode', 'absolute')]
                        + [tvid, prodid]
                        + [entry.get(xem_origin).get(v) for v in ('season', 'episode')]
                ], filter(lambda x: 'scene' in x, parsed_json['data'])))

                if 0 < len(cl):
                    my_db = db.DBConnection()
                    my_db.mass_action(cl)
            else:
                logger.debug(f'Empty lookup result - no XEM data for show {prodid} on {tvinfo.name}')
        except (BaseException, Exception) as e:
            logger.warning(f'Exception refreshing XEM data for show {str(prodid)} on {tvinfo.name}: {ex(e)}')
            logger.error(traceback.format_exc())


def fix_xem_numbering(tvid, prodid):
    """

    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    """
    if None is prodid:
        return {}

    tvid, prodid = int(tvid), int(prodid)

    my_db = db.DBConnection()
    sql_result = my_db.select(
        """
        SELECT season, episode, absolute_number, scene_season, scene_episode, scene_absolute_number
        FROM tv_episodes
        WHERE indexer = ? AND showid = ?
        """, [tvid, prodid])

    last_absolute_number = None
    last_scene_season = None
    last_scene_episode = None
    last_scene_absolute_number = None

    update_absolute_number = False
    update_scene_season = False
    update_scene_episode = False
    update_scene_absolute_number = False

    logger.debug(f'Fixing any XEM scene mapping issues for show {prodid} on {sickgear.TVInfoAPI(tvid).name}')

    cl = []
    for cur_row in sql_result:
        season = int(cur_row['season'])
        episode = int(cur_row['episode'])

        if not int(cur_row['scene_season']) and last_scene_season:
            scene_season = last_scene_season + 1
            update_scene_season = True
        else:
            scene_season = int(cur_row['scene_season'])
            if last_scene_season and scene_season < last_scene_season:
                scene_season = last_scene_season + 1
                update_scene_season = True

        if not int(cur_row['scene_episode']) and last_scene_episode:
            scene_episode = last_scene_episode + 1
            update_scene_episode = True
        else:
            scene_episode = int(cur_row['scene_episode'])
            if last_scene_episode and scene_episode < last_scene_episode:
                scene_episode = last_scene_episode + 1
                update_scene_episode = True

        # check for unset values and correct them
        if not int(cur_row['absolute_number']) and last_absolute_number:
            absolute_number = last_absolute_number + 1
            update_absolute_number = True
        else:
            absolute_number = int(cur_row['absolute_number'])
            if last_absolute_number and absolute_number < last_absolute_number:
                absolute_number = last_absolute_number + 1
                update_absolute_number = True

        if not int(cur_row['scene_absolute_number']) and last_scene_absolute_number:
            scene_absolute_number = last_scene_absolute_number + 1
            update_scene_absolute_number = True
        else:
            scene_absolute_number = int(cur_row['scene_absolute_number'])
            if last_scene_absolute_number and scene_absolute_number < last_scene_absolute_number:
                scene_absolute_number = last_scene_absolute_number + 1
                update_scene_absolute_number = True

        # store values for lookup on next iteration
        last_absolute_number = absolute_number
        last_scene_season = scene_season
        last_scene_episode = scene_episode
        last_scene_absolute_number = scene_absolute_number

        if update_absolute_number:
            cl.append([
                """
                UPDATE tv_episodes
                SET absolute_number = ?
                WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?
                """, [absolute_number, tvid, prodid, season, episode]
            ])
            update_absolute_number = False

        if update_scene_season:
            cl.append([
                """
                UPDATE tv_episodes
                SET scene_season = ?
                WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?
                """, [scene_season, tvid, prodid, season, episode]
            ])
            update_scene_season = False

        if update_scene_episode:
            cl.append([
                """
                UPDATE tv_episodes
                SET scene_episode = ?
                WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?
                """, [scene_episode, tvid, prodid, season, episode]
            ])
            update_scene_episode = False

        if update_scene_absolute_number:
            cl.append([
                """
                UPDATE tv_episodes
                SET scene_absolute_number = ?
                WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?
                """, [scene_absolute_number, tvid, prodid, season, episode]
            ])
            update_scene_absolute_number = False

    if 0 < len(cl):
        my_db = db.DBConnection()
        my_db.mass_action(cl)


def set_scene_numbering_helper(tvid, prodid, for_season=None, for_episode=None, for_absolute=None,
                               scene_season=None, scene_episode=None, scene_absolute=None):
    """

    :param tvid: tvid
    :type tvid: int
    :param prodid: prodid
    :type prodid: int or long
    :param for_season:
    :type for_season: int or None
    :param for_episode:
    :type for_episode: int or None
    :param for_absolute:
    :type for_absolute: int or None
    :param scene_season:
    :type scene_season: int or None
    :param scene_episode:
    :type scene_episode: int or None
    :param scene_absolute:
    :type scene_absolute: int or None
    :return:
    :rtype: Dict[AnyStr, int]
    """
    # sanitize:
    tvid = None if tvid in [None, 'null', ''] else int(tvid)
    prodid = None if prodid in [None, 'null', ''] else int(prodid)

    show_obj = sickgear.helpers.find_show_by_id({tvid: prodid}, no_mapped_ids=True)
    if not show_obj:
        return {'success': False}

    for_season = None if for_season in [None, 'null', ''] else int(for_season)
    for_episode = None if for_episode in [None, 'null', ''] else int(for_episode)
    ep_args = {'show': prodid, 'season': for_season, 'episode': for_episode}
    scene_args = {'tvid': tvid, 'prodid': prodid, 'season': for_season, 'episode': for_episode}
    if not show_obj.is_anime:
        scene_season = None if scene_season in [None, 'null', ''] else int(scene_season)
        scene_episode = None if scene_episode in [None, 'null', ''] else int(scene_episode)
        action_log = f'Set episode scene numbering to {scene_season}x{scene_episode}' \
                     f' for episode {for_season}x{for_episode} of "{show_obj.unique_name}"'
        scene_args.update({'scene_season': scene_season, 'scene_episode': scene_episode})
        result = {'forSeason': for_season, 'forEpisode': for_episode, 'sceneSeason': None, 'sceneEpisode': None}
    else:
        for_absolute = None if for_absolute in [None, 'null', ''] else int(for_absolute)
        scene_absolute = None if scene_absolute in [None, 'null', ''] else int(scene_absolute)
        action_log = f'Set absolute scene numbering to {scene_absolute}' \
                     f' for episode {for_season}x{for_episode} of "{show_obj.unique_name}"'
        ep_args.update({'absolute': for_absolute})
        scene_args.update({'absolute_number': for_absolute, 'scene_absolute': scene_absolute, 'anime': True})
        result = {'forAbsolute': for_absolute, 'sceneAbsolute': None}

    if ep_args.get('absolute'):
        ep_obj = show_obj.get_episode(absolute_number=int(ep_args['absolute']))
    elif None is not ep_args['season'] and None is not ep_args['episode']:
        ep_obj = show_obj.get_episode(int(ep_args['season']), int(ep_args['episode']))
    else:
        ep_obj = 'Invalid parameters'

    result['success'] = None is not ep_obj and not isinstance(ep_obj, str)
    if result['success']:
        logger.debug(action_log)
        set_scene_numbering(**scene_args)
        show_obj.flush_episodes()
        if not show_obj.is_anime:
            if (None is scene_season and None is scene_episode) or (0 == scene_season and 0 == scene_episode):
                # when clearing the field, do not return existing values of sxe, otherwise this may be confusing
                # with the case where manually setting sxe to the actual sxe is done to prevent a data overwrite.
                # So now the only instance an actual sxe is in the field is if user enters it, else 0x0 is presented.
                return result
        elif None is scene_absolute or 0 == scene_absolute:
            return result
    else:
        result['errorMessage'] = "Episode couldn't be retrieved, invalid parameters"

    if not show_obj.is_anime:
        scene_numbering = get_scene_numbering(tvid, prodid, for_season, for_episode, show_obj=show_obj)
        if scene_numbering:
            (result['sceneSeason'], result['sceneEpisode']) = scene_numbering
    else:
        scene_numbering = get_scene_absolute_numbering(tvid, prodid, for_absolute, for_season, for_episode,
                                                       show_obj=show_obj)
        if scene_numbering:
            result['sceneAbsolute'] = scene_numbering

    return result
