# coding=UTF-8
# Author:
# URL: https://github.com/SickGear/SickGear
#
# 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/>.
import datetime
import itertools
import os
import unittest
import warnings
import sys
import test_lib as test

warnings.filterwarnings('ignore', module=r'.*ssl_.*', message='.*SSLContext object.*')

import sickgear
from exceptions_helper import ex
from sickgear.classes import SearchResult
from sickgear.common import Quality, ARCHIVED, DOWNLOADED, WANTED, UNAIRED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, \
    SNATCHED_ANY, SUBTITLED, statusStrings, UNKNOWN
from sickgear.event_queue import Events
from sickgear.tv import TVEpisode, TVShow
from sickgear.webserveInit import WebServer
from sickgear import webapi, scheduler, search_backlog, search_queue, show_queue, history, db
from sickgear.scene_numbering import set_scene_numbering_helper
from lib import requests
from six import integer_types, iteritems, iterkeys, itervalues, string_types

# noinspection PyUnreachableCode
if False:
    from typing import Any, AnyStr

NoneType = type(None)
today = datetime.date.today()
last_week = today - datetime.timedelta(days=5)
old_date = today - datetime.timedelta(days=14)
future = today + datetime.timedelta(days=1)
far_future = today + datetime.timedelta(days=30)

if 'win32' == sys.platform:
    root_folder_tests = [
        # root_dirs, path, expected
        ('1|C:\\dir', 'C:\\folder', None),
        ('1|c:\\dir', 'c:\\dir', 'c:\\dir'),
        ('1|c:\\dir2', 'c:\\dir2\\dir', 'c:\\dir2'),
        ('1|c:\\tv_complete|c:\\tv', 'c:\\tv', 'c:\\tv')
    ]
else:
    root_folder_tests = [
        # root_dirs, path, expected
        ('1|~/dir', '~/dir/dir', '~/dir'),
        ('1|/mnt/hdd/dir', '/mnt/hdd/folder', None),
        ('1|/mnt/hdd/dir', '/mnt/hdd/dir', '/mnt/hdd/dir'),
        ('1|/mnt/hdd/dir2', '/mnt/hdd/dir2/dir', '/mnt/hdd/dir2'),
        ('1|/mnt/hdd/tv_complete|/mnt/hdd/tv', '/mnt/hdd/tv', '/mnt/hdd/tv')
    ]

test_shows = [
    {'tvid': 1, 'prodid': 1234, 'name': 'Test Show', 'runtime': 45, 'airs': 'Mondays, 00:45', 'imdbid': 'tt1234567',
     '_location': r'C:\series\show dir', 'network': 'Network', 'overview': 'Overview text', 'status': 'Continuing',
     'quality_init': [], 'quality_upgrade': [],
     'episodes': {
                1: {
                    1: {'name': 'ep1', 'status': Quality.composite_status(DOWNLOADED, Quality.HDWEBDL),
                        'airdate': old_date, 'description': 'ep1 description'},
                    2: {'name': 'ep2', 'status': WANTED, 'airdate': last_week, 'description': 'ep2 description'},
                    3: {'name': 'ep3', 'status': WANTED, 'airdate': today, 'description': 'ep3 description'},
                    4: {'name': 'ep4', 'status': UNAIRED, 'airdate': future, 'description': 'ep4 description'},
                    5: {'name': 'ep5', 'status': UNAIRED, 'airdate': far_future, 'description': 'ep5 description'},
                }
            }
     },
    {'tvid': 1, 'prodid': 5678, 'name': 'Test Show 2', 'runtime': 45, 'airs': 'Tuesdays, 22:15', 'imdbid': 'tt7775567',
     '_location': r'C:\series\show 2', 'network': 'Network 2', 'overview': 'Overview text 2', 'status': 'Continuing',
     'quality_init': [Quality.HDTV, Quality.FULLHDWEBDL], 'quality_upgrade': [Quality.FULLHDWEBDL],
     'episodes': {
                1: {
                    1: {'name': 'new ep1', 'status': UNAIRED, 'airdate': far_future, 'description': 'ep1 description'},
                    2: {'name': 'new ep2', 'status': UNAIRED, 'airdate': far_future + datetime.timedelta(days=7),
                        'description': 'ep2 description'},
                    3: {'name': 'new ep3', 'status': UNAIRED, 'airdate': far_future + datetime.timedelta(days=14),
                        'description': 'ep3 description'},
                    4: {'name': 'new ep4', 'status': UNAIRED, 'airdate': far_future + datetime.timedelta(days=28),
                        'description': 'ep4 description'},
                    5: {'name': 'new ep5', 'status': UNAIRED, 'description': 'ep5 description'},
                }
            }
     },
]


def fake_action(*args, **kwargs):
    pass


class fake_class():
    pass


class WebAPICase(test.SickbeardTestDBCase):
    webserver = None
    instance = None

    def __init__(self, *args, **kwargs):
        super(WebAPICase, self).__init__(*args, **kwargs)
        self.org_mass_action = None
        self.show_save_db = None

    @classmethod
    def setUpClass(cls):
        super(WebAPICase, cls).setUpClass()
        # web server options
        sickgear.WEB_PORT = 8080
        cls.web_options = dict(
            host='127.0.0.1',
            port=sickgear.WEB_PORT,
            web_root=None,
            data_root=os.path.join(sickgear.PROG_DIR, 'gui', sickgear.GUI_NAME),
            log_dir=sickgear.LOG_DIR,
            username=sickgear.WEB_USERNAME,
            password=sickgear.WEB_PASSWORD,
            handle_reverse_proxy=sickgear.HANDLE_REVERSE_PROXY,
            enable_https=False,
            https_cert=None,
            https_key=None,
        )
        # start web server
        try:
            # used to check if existing SG instances have been started
            sickgear.helpers.wait_for_free_port(
                sickgear.WEB_IPV6 and '::1' or cls.web_options['host'], cls.web_options['port'])

            cls.webserver = WebServer(options=cls.web_options)
            cls.webserver.start()
            # wait for server thread to be started
            cls.webserver.wait_server_start()
            cls.webserver.switch_handlers()
            sickgear.started = True
            sickgear.API_KEYS = [['unit test key', '1234567890']]
            sickgear.USE_API = True
            sickgear.process_media_scheduler = fake_class()
            sickgear.update_show_scheduler = fake_class()
            sickgear.process_media_scheduler.is_running_job = False
            sickgear.update_show_scheduler.is_running_job = False
        except (BaseException, Exception) as e:
            print('Failed to start WebServer: %s' % ex(e))

    @classmethod
    def tearDownClass(cls):
        super(WebAPICase, cls).tearDownClass()
        # shutdown web server
        if cls.webserver:
            cls.webserver.shut_down()
            try:
                cls.webserver.join(10)
            except (BaseException, Exception):
                pass
        if cls.instance:
            super(WebAPICase, cls.instance).tearDown()

    def setUp(self):
        self.reset_show_data = False
        self.org_mass_action = None
        self.show_save_db = None
        if not WebAPICase.instance:
            WebAPICase.instance = self
            super(WebAPICase, self).setUp()
            sickgear.events = Events(None)
            sickgear.show_queue_scheduler = scheduler.Scheduler(
                show_queue.ShowQueue(),
                cycle_time=datetime.timedelta(seconds=3),
                thread_name='SHOWQUEUE')
            sickgear.search_queue_scheduler = scheduler.Scheduler(
                search_queue.SearchQueue(),
                cycle_time=datetime.timedelta(seconds=3),
                thread_name='SEARCHQUEUE')
            sickgear.search_backlog_scheduler = search_backlog.BacklogSearchScheduler(
                search_backlog.BacklogSearcher(),
                cycle_time=datetime.timedelta(minutes=60),
                run_delay=datetime.timedelta(minutes=60),
                thread_name='BACKLOG')
            sickgear.indexermapper.indexer_list = [i for i in sickgear.indexers.indexer_api.TVInfoAPI().all_sources]
            for root_dirs, path, expected in root_folder_tests:
                sickgear.ROOT_DIRS = root_dirs
            for cur_show in test_shows:
                show_obj = TVShow(cur_show['tvid'], cur_show['prodid'])
                for k, v in iteritems(cur_show):
                    if k in ('tvid', 'prodid', 'episodes', 'quality_init', 'quality_upgrade'):
                        continue
                    if '_%s' % k in show_obj.__dict__:
                        show_obj.__dict__['_%s' % k] = v
                    elif k in show_obj.__dict__:
                        show_obj.__dict__[k] = v
                if 'quality_init' in cur_show and cur_show['quality_init']:
                    show_obj.quality = Quality.combine_qualities(cur_show['quality_init'],
                                                                 cur_show.get('quality_upgrade', []))
                show_obj.dirty = True

                show_obj.save_to_db(True)
                sickgear.showList.append(show_obj)
                sickgear.showDict.update({show_obj.sid_int: show_obj})

                for season, eps in iteritems(cur_show['episodes']):
                    for ep, data in iteritems(eps):
                        ep_obj = TVEpisode(show_obj, season, ep)
                        for k, v in iteritems(data):
                            if '_%s' % k in ep_obj.__dict__:
                                ep_obj.__dict__['_%s' % k] = v
                            elif k in ep_obj.__dict__:
                                ep_obj.__dict__[k] = v
                        show_obj.sxe_ep_obj.setdefault(season, {})[ep] = ep_obj
                        ep_obj.save_to_db(True)
                        status, quality = Quality.split_composite_status(ep_obj.status)
                        if status in (DOWNLOADED, SNATCHED):
                            s_r = SearchResult([ep_obj])
                            s_r.show_obj, s_r.quality, s_r.provider, s_r.name = \
                                show_obj, quality, None, '%s.S%sE%s.group' % (
                                    show_obj.name, ep_obj.season, ep_obj.episode)
                            history.log_snatch(s_r)
                            if DOWNLOADED == status:
                                history.log_download(ep_obj, '%s.S%sE%s.group.mkv' % (
                                    show_obj.name, ep_obj.season, ep_obj.episode), quality, 'group')

            sickgear.webserve.Home.make_showlist_unique_names()

    def tearDown(self):
        if None is not self.org_mass_action:
            db.DBConnection.mass_action = self.org_mass_action
        if None is not self.show_save_db:
            sickgear.tv.TVShow.save_to_db = self.show_save_db
        sickgear.show_queue_scheduler.action.queue = []
        sickgear.search_queue_scheduler.action.queue = []
        if self.reset_show_data:
            for cur_show in test_shows:
                show_obj = sickgear.helpers.find_show_by_id({cur_show['tvid']: cur_show['prodid']})
                if 'quality_init' in cur_show and cur_show['quality_init']:
                    show_obj.quality = Quality.combine_qualities(cur_show['quality_init'],
                                                                 cur_show.get('quality_upgrade', []))
                else:
                    show_obj.quality = int(sickgear.QUALITY_DEFAULT)
                show_obj.upgrade_once = int(cur_show.get('upgrade_once', 0))
                show_obj.scene = int(cur_show.get('scene', 0))
                show_obj.save_to_db()
                for season, data in iteritems(cur_show['episodes']):
                    for ep_nb, cur_ep in iteritems(data):
                        ep_obj = show_obj.get_episode(season, ep_nb)
                        ep_obj.status = cur_ep.get('status')
                        ep_obj.save_to_db()
                        set_scene_numbering_helper(
                            cur_show['tvid'], cur_show['prodid'], season, ep_nb,
                            scene_season=cur_ep.get('scene_season'), scene_episode=cur_ep.get('scene_episode'))

    @staticmethod
    def _request_from_api(cmd, params=None):
        param = {'cmd': webapi._functionMaper_reversed[cmd]}
        if isinstance(params, dict):
            param.update(params)
        return requests.get('http://127.0.0.1:%s/api/%s' % (sickgear.WEB_PORT, sickgear.API_KEYS[0][1]),
                            params=param).json()

    @staticmethod
    def _check_types(data, fields):
        result, msg = True, []
        missing_list, wrong_type = [], {}
        for f in fields:
            if f[0] not in data:
                missing_list.append(f[0])
                result = False
            elif not isinstance(data[f[0]], f[1]):
                wrong_type[f[0]] = 'Expected: %s, Got: %s' % (type(data[f[0]]), str(f[1]))
                result = False
        if missing_list:
            msg.append('Missing fields: %s' % ', '.join(missing_list))
        if wrong_type:
            msg.append('Wrong field type: %s' % ', '.join(['%s: %s' % (k, v) for k, v in iteritems(wrong_type)]))
        return result, ('', ', %s' % ', '.join(msg))[0 < len(msg)]

    def _check_success_base_response(self, data, endpoint, message='', data_type=dict):
        # type: (Any, Any, AnyStr, type) -> None
        r, msg = self._check_types(data, [('result', string_types), ('data', data_type), ('message', string_types)])
        self.assertTrue(r, msg='Failed command: %s%s' % (webapi._functionMaper_reversed[endpoint], msg))
        self.assertEqual(data['result'], 'success')
        self.assertEqual(data['message'], message)

    def _check_episode_data(self, data, endpoint):
        r, msg = self._check_types(
            data,
            [('absolute_number', integer_types), ('airdate', string_types), ('description', string_types),
             ('name', string_types), ('quality', string_types), ('scene_absolute_number', integer_types),
             ('scene_episode', integer_types), ('scene_season', integer_types), ('status', string_types),
             ('timezone', (NoneType, string_types))]
        )
        self.assertTrue(r, msg='Failed command: %s - data episode dict%s' % (
            webapi._functionMaper_reversed[endpoint], msg))

    def test_sg(self):
        data = self._request_from_api(webapi.CMD_SickGear)
        r, msg = self._check_types(data, [('data', dict), ('message', string_types), ('result', string_types)])
        self.assertTrue(r, msg='Failed command: %s%s' % (webapi._functionMaper_reversed[webapi.CMD_SickGear], msg))
        r, msg = self._check_types(data['data'], [('api_commands', list), ('api_version', integer_types),
                                             ('fork', string_types), ('sb_version', string_types)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGear], msg))
        needed_ele = [(k, string_types) for k in iterkeys(webapi._functionMaper) if 'listcommands' != k]
        r = all(v[0] in data['data']['api_commands'] for v in needed_ele)
        if not r:
            i = list(set(n[0] for n in needed_ele) - set(data['data']['api_commands']))
        else:
            i = []
        self.assertTrue(r, msg='Failed command: %s - api_commands list%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGear], ('', ', missing: %s' % ','.join(i))[0 < len(i)]))

    def _check_show_fields(self, data, endpoint, include_season_list=True):
        r, msg = self._check_types(
            data,
            [('air_by_date', integer_types), ('airs', string_types), ('anime', integer_types), ('cache', dict),
             ('classification', string_types), ('flatten_folders', integer_types), ('genre', list),
             ('global_exclude_ignore', string_types), ('global_exclude_require', string_types), ('ids', dict),
             ('ignorewords', string_types), ('imdb_id', string_types), ('indexer', integer_types),
             ('indexerid', integer_types), ('language', string_types), ('location', string_types),
             ('network', string_types), ('next_ep_airdate', string_types), ('paused', integer_types),
             ('prune', integer_types), ('quality', string_types), ('quality_details', dict),
             ('requirewords', string_types), ('runtime', integer_types), ('scenenumbering', bool),
             ('show_name', string_types), ('sports', integer_types), ('startyear', integer_types),
             ('status', string_types), ('subtitles', integer_types), ('tag', string_types),
             ('timezone', (NoneType, string_types)), ('tvrage_id', integer_types),
             ('tvrage_name', string_types), ('upgrade_once', integer_types)] +
            ([], [('season_list', list)])[include_season_list]
        )
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[endpoint], msg))
        r, msg = self._check_types(data['ids'],
                                   [('%s' % k, integer_types) for k in sickgear.indexermapper.indexer_list])
        self.assertTrue(r, msg='Failed shows "ids" check: %s' % msg)
        r, msg = self._check_types(
            data['quality_details'],
            [('archive', list), ('initial', list)]
        )
        self.assertTrue(r, msg='Failed shows "quality_details" check: %s' % msg)

    def test_show(self):
        # test not found
        data = self._request_from_api(webapi.CMD_SickGearShow, params={'indexer': 1, 'indexerid': 98765})
        r, msg = self._check_types(data, [('result', string_types), ('data', dict), ('message', string_types)])
        self.assertTrue(r, msg='Failed command: %s%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearShow], msg))
        self.assertEqual(data['result'], 'failure')
        self.assertEqual(data['message'], 'Show not found')
        # test existing show
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShow,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearShow)
            self._check_show_fields(data['data'], webapi.CMD_SickGearShow)

    def test_seasons(self):
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShowSeasons,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearShowSeasons)
            for season, eps in iteritems(cur_show['episodes']):
                r, msg = self._check_types(data['data'], [('%s' % season, dict)])
                self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                    webapi._functionMaper_reversed[webapi.CMD_SickGearShowSeasons], msg))
                for cur_ep in iterkeys(eps):
                    r, msg = self._check_types(data['data']['%s' % season], [('%s' % cur_ep, dict)])
                    self.assertTrue(r, msg='Failed command: %s - data season dict%s' % (
                        webapi._functionMaper_reversed[webapi.CMD_SickGearShowSeasons], msg))
                    self._check_episode_data(data['data']['%s' % season]['%s' % cur_ep], webapi.CMD_SickGearShowSeasons)

    def test_coming_episodes(self):
        data = self._request_from_api(webapi.CMD_SickGearComingEpisodes)
        self._check_success_base_response(data, webapi.CMD_SickGearComingEpisodes)
        r, msg = self._check_types(data['data'], [('later', list), ('missed', list), ('soon', list), ('today', list)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearComingEpisodes], msg))
        self.assertTrue(all(0 < len(data['data'][t]) for t in ('later', 'missed', 'soon', 'today')),
                        msg='Not all categories returned')
        for t_p in ('later', 'missed', 'soon', 'today'):
            for cur_ep in data['data'][t_p]:
                r, msg = self._check_types(
                    cur_ep,
                    [('airdate', string_types), ('airs', string_types), ('data_network', string_types),
                     ('data_show_name', string_types), ('ep_name', string_types), ('ep_plot', string_types),
                     ('episode', integer_types), ('ids', dict), ('local_datetime', string_types),
                     ('network', string_types), ('parsed_datetime', string_types), ('paused', integer_types),
                     ('prod_id', integer_types), ('quality', string_types), ('runtime', integer_types),
                     ('season', integer_types), ('show_name', string_types), ('show_status', string_types),
                     ('status', integer_types), ('status_str', string_types), ('timezone', (NoneType, string_types)),
                     ('tv_id', integer_types), ('tvdbid', integer_types), ('weekday', integer_types)]
                )
                self.assertTrue(r, msg='Failed command: %s - data %s dict%s' % (
                    webapi._functionMaper_reversed[webapi.CMD_SickGearComingEpisodes], t_p, msg))
                r, msg = self._check_types(cur_ep['ids'],
                                           [('%s' % k, integer_types) for k in sickgear.indexermapper.indexer_list])
                self.assertTrue(r, msg='Failed %s "ids" check: %s' % (t_p, msg))

    def test_all_shows(self):
        data = self._request_from_api(webapi.CMD_SickGearShows)
        self._check_success_base_response(data, webapi.CMD_SickGearShows)
        for show_id, cur_show in iteritems(data['data']):
            self._check_show_fields(cur_show, webapi.CMD_SickGearShows, include_season_list=False)

    def test_episode(self):
        # not found episode
        data = self._request_from_api(webapi.CMD_SickGearEpisode,
                                      params={'indexer': 1, 'indexerid': 1234, 'season': 10, 'episode': 11})
        r, msg = self._check_types(data, [('result', string_types), ('data', dict), ('message', string_types)])
        self.assertTrue(r, msg='Failed command: %s%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearEpisode], msg))
        self.assertEqual(data['result'], 'error')
        self.assertEqual(data['message'], 'Episode not found')
        # found episode
        for cur_show in test_shows:
            for season, eps in iteritems(cur_show['episodes']):
                for cur_ep in iterkeys(eps):
                    data = self._request_from_api(webapi.CMD_SickGearEpisode,
                                                  params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid'],
                                                          'season': season, 'episode': cur_ep})
                    self._check_success_base_response(data, webapi.CMD_SickGearEpisode)
                    r, msg = self._check_types(
                        data['data'],
                        [('absolute_number', integer_types), ('airdate', string_types), ('description', string_types),
                         ('file_size', integer_types), ('file_size_human', string_types), ('location', string_types),
                         ('name', string_types), ('quality', string_types), ('release_name', string_types),
                         ('scene_absolute_number', (NoneType, integer_types)),
                         ('scene_episode', (NoneType, integer_types)), ('scene_season', (NoneType, integer_types)),
                         ('status', string_types), ('subtitles', string_types), ('timezone', (NoneType, string_types))]
                    )
                    self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                        webapi._functionMaper_reversed[webapi.CMD_SickGearEpisode], msg))

    def test_shutdown(self):
        data = self._request_from_api(webapi.CMD_SickGearShutdown)
        r, msg = self._check_types(data, [('data', dict), ('message', string_types), ('result', string_types)])
        self.assertTrue(r, msg='basic test failed for shutdown')
        self.assertEqual(data['message'], 'SickGear is shutting down...')
        self.assertEqual(data['result'], 'success')

    def test_restart(self):
        data = self._request_from_api(webapi.CMD_SickGearRestart)
        r, msg = self._check_types(data, [('data', dict), ('message', string_types), ('result', string_types)])
        self.assertTrue(r, msg='basic test failed for shutdown')
        self.assertEqual(data['message'], 'SickGear is restarting...')
        self.assertEqual(data['result'], 'success')

    def test_ping(self):
        data = self._request_from_api(webapi.CMD_SickGearPing)
        r, msg = self._check_types(data, [('data', dict), ('message', string_types), ('result', string_types)])
        self.assertTrue(r, msg='basic test failed for shutdown')
        self.assertEqual(data['message'], 'Pong')
        self.assertEqual(data['result'], 'success')

    def test_get_indexers(self):
        data = self._request_from_api(webapi.CMD_SickGearGetIndexers)
        self._check_success_base_response(data, webapi.CMD_SickGearGetIndexers)
        r, msg = self._check_types(data['data'], [('%s' % k, dict) for k in sickgear.indexermapper.indexer_list])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearGetIndexers], msg))
        for i in sickgear.indexermapper.indexer_list:
            r, msg = self._check_types(
                data['data']['%s' % i],
                [('id', integer_types), ('main_url', string_types), ('name', string_types), ('searchable', bool),
                 ('show_url', string_types)])
            self.assertTrue(r, msg='Failed command: %s - data %s dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearGetIndexers], i, msg))

    def test_get_seasonlist(self):
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShowSeasonList,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearShowSeasonList, data_type=list)
            r = all(isinstance(v, integer_types) for v in data['data'])
            self.assertTrue(r, msg='Failed command: %s - data dict incorrect type' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearShowSeasonList]))

    def test_get_episodelist(self):
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShowSeasons,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearShowSeasons)
            r, msg = self._check_types(data['data'], [('%s' % i, dict) for i in iterkeys(cur_show['episodes'])])
            self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearShowSeasons], msg))
            for season, eps in iteritems(cur_show['episodes']):
                r, msg = self._check_types(data['data'], [('%s' % season, dict)])
                self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                    webapi._functionMaper_reversed[webapi.CMD_SickGearShowSeasons], msg))
                for cur_ep in iterkeys(eps):
                    r, msg = self._check_types(data['data']['%s' % season], [('%s' % cur_ep, dict)])
                    self.assertTrue(r, msg='Failed command: %s - data season dict%s' % (
                        webapi._functionMaper_reversed[webapi.CMD_SickGearShowSeasons], msg))
                    self._check_episode_data(data['data']['%s' % season]['%s' % cur_ep], webapi.CMD_SickGearShowSeasons)

    def test_get_require_words(self):
        # global
        data = self._request_from_api(webapi.CMD_SickGearListRequireWords)
        self._check_success_base_response(data, webapi.CMD_SickGearListRequireWords, message='Global require word list')
        r, msg = self._check_types(data['data'], [('require words', list), ('type', string_types), ('use regex', bool)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearListRequireWords], msg))
        # show based
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearListRequireWords,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearListRequireWords,
                                              message='%s: require word list' % cur_show['name'])
            r, msg = self._check_types(
                data['data'],
                [('require words', list), ('type', string_types), ('use regex', bool),
                 ('global exclude require', list), ('indexer', integer_types), ('indexerid', integer_types),
                 ('show name', string_types)])
            self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearListRequireWords], msg))

    def test_get_ignore_words(self):
        # global
        data = self._request_from_api(webapi.CMD_SickGearListIgnoreWords)
        self._check_success_base_response(data, webapi.CMD_SickGearListIgnoreWords, message='Global ignore word list')
        r, msg = self._check_types(data['data'], [('ignore words', list), ('type', string_types), ('use regex', bool)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearListIgnoreWords], msg))
        # show based
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearListIgnoreWords,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearListIgnoreWords,
                                              message='%s: ignore word list' % cur_show['name'])
            r, msg = self._check_types(
                data['data'],
                [('ignore words', list), ('type', string_types), ('use regex', bool),
                 ('global exclude ignore', list), ('indexer', integer_types), ('indexerid', integer_types),
                 ('show name', string_types)])
            self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearListIgnoreWords], msg))

    def test_get_search_queue(self):
        data = self._request_from_api(webapi.CMD_SickGearSearchQueue)
        self._check_success_base_response(data, webapi.CMD_SickGearSearchQueue)
        r, msg = self._check_types(
            data['data'],
            [(t, list) for t in ('backlog', 'failed', 'manual', 'proper')] + [('recent', integer_types)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearSearchQueue], msg))

    def test_get_system_default(self):
        data = self._request_from_api(webapi.CMD_SickGearGetDefaults)
        self._check_success_base_response(data, webapi.CMD_SickGearGetDefaults)
        r, msg = self._check_types(
            data['data'],
            [('archive', list), ('flatten_folders', integer_types), ('future_show_paused', integer_types),
             ('initial', list), ('status', string_types)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearGetDefaults], msg))

    def test_get_all_qualities(self):
        data = self._request_from_api(webapi.CMD_SickGearGetQualities)
        self._check_success_base_response(data, webapi.CMD_SickGearGetQualities)
        r, msg = self._check_types(data['data'], [(q, integer_types) for q in iterkeys(webapi.quality_map)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearGetQualities], msg))

    def test_get_human_qualities(self):
        data = self._request_from_api(webapi.CMD_SickGearGetqualityStrings)
        self._check_success_base_response(data, webapi.CMD_SickGearGetqualityStrings)
        r, msg = self._check_types(data['data'], [('%s' % q, string_types) for q in iterkeys(Quality.qualityStrings)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearGetqualityStrings], msg))

    def test_get_scene_qualities(self):
        # global
        data = self._request_from_api(webapi.CMD_SickGearExceptions)
        self._check_success_base_response(data, webapi.CMD_SickGearExceptions)
        r = all(isinstance(e, string_types) for e in data['data'])
        self.assertTrue(r, msg='Failed command: %s - data dict' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearExceptions]))
        # show specific
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearExceptions,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearExceptions, data_type=list)
            r = all(isinstance(e, string_types) for e in data['data'])
            self.assertTrue(r, msg='Failed command: %s - data dict' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearExceptions]))

    def test_history(self):
        data = self._request_from_api(webapi.CMD_SickGearHistory)
        self._check_success_base_response(data, webapi.CMD_SickGearHistory, data_type=list)
        for cur_show in data['data']:
            self.assertTrue(isinstance(cur_show, dict), msg='wrong type')
            r, msg = self._check_types(
                cur_show,
                [('date', string_types), ('episode', integer_types), ('indexer', integer_types),
                 ('indexerid', integer_types), ('provider', string_types), ('quality', string_types),
                 ('resource', string_types), ('resource_path', string_types), ('season', integer_types),
                 ('show_name', string_types), ('status', string_types), ('tvdbid', integer_types),
                 ('version', integer_types)])
            self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearHistory], msg))

    def test_shows_stats(self):
        data = self._request_from_api(webapi.CMD_SickGearShowsStats)
        self._check_success_base_response(data, webapi.CMD_SickGearShowsStats)
        r, msg = self._check_types(
            data['data'],
            [('ep_downloaded', integer_types), ('ep_total', integer_types), ('shows_active', integer_types),
             ('shows_total', integer_types)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearShowsStats], msg))

    def test_show_stats(self):
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShowStats,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearShowStats)
            r, msg = self._check_types(
                data['data'],
                [(statusStrings.statusStrings[status].lower().replace(" ", "_").replace("(", "").replace(
                    ")", ""), integer_types) for status in statusStrings.statusStrings
                 if status not in SNATCHED_ANY + [UNKNOWN, DOWNLOADED]] + [('downloaded', dict), ('snatched', dict)])
            self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearShowStats], msg))
            for s in ('downloaded', 'snatched'):
                r, msg = self._check_types(
                    data['data'][s],
                    [(t.lower().replace(" ", "_").replace("(", "").replace(")", ""), integer_types)
                     for k, t in iteritems(Quality.qualityStrings) if Quality.NONE != k])
                self.assertTrue(r, msg='Failed command: %s - data dict - %s:%s' % (
                    webapi._functionMaper_reversed[webapi.CMD_SickGearShowStats], s, msg))

    def test_get_root_dirs(self):
        data = self._request_from_api(webapi.CMD_SickGearGetRootDirs)
        self._check_success_base_response(data, webapi.CMD_SickGearGetRootDirs, data_type=list)
        for r_d in data['data']:
            r, msg = self._check_types(r_d, [('default', integer_types), ('location', string_types),
                                             ('valid', integer_types)])
            self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearListIgnoreWords], msg))

    def test_get_schedules(self):
        data = self._request_from_api(webapi.CMD_SickGearCheckScheduler)
        self._check_success_base_response(data, webapi.CMD_SickGearCheckScheduler)
        r, msg = self._check_types(
            data['data'],
            [('backlog_is_paused', integer_types), ('backlog_is_running', integer_types),
             ('last_backlog', string_types), ('next_backlog', string_types)])
        self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
            webapi._functionMaper_reversed[webapi.CMD_SickGearCheckScheduler], msg))

    def test_do_show_update(self):
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShowUpdate,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearShowUpdate,
                                              message='%s has queued to be updated' % cur_show['name'])
        # check that duplicate adding fails
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShowUpdate,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            r, msg = self._check_types(data, [('data', dict), ('message', string_types), ('result', string_types)])
            self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearShowUpdate], msg))
            self.assertTrue('Unable to update %s.' % cur_show['name'] in data['message'], msg='Wrong failure message')

    def test_do_show_refresh(self):
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShowRefresh,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            self._check_success_base_response(data, webapi.CMD_SickGearShowUpdate,
                                              message='%s has queued to be refreshed' % cur_show['name'])
        # check that duplicate adding fails
        for cur_show in test_shows:
            data = self._request_from_api(webapi.CMD_SickGearShowRefresh,
                                          params={'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']})
            r, msg = self._check_types(data, [('data', dict), ('message', string_types), ('result', string_types)])
            self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                webapi._functionMaper_reversed[webapi.CMD_SickGearShowRefresh], msg))
            self.assertTrue('Unable to refresh %s.' % cur_show['name'] in data['message'], msg='Wrong failure message')

    def test_pause_show(self):
        self.reset_show_data = True
        self.show_save_db = sickgear.tv.TVShow.save_to_db
        sickgear.tv.TVShow.save_to_db = fake_action
        for set_pause in (None, 0, 1, 0):
            for cur_show in test_shows:
                params = {'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']}
                if None is not set_pause:
                    params.update({'pause': set_pause})
                data = self._request_from_api(webapi.CMD_SickGearShowPause, params=params)
                self._check_success_base_response(data, webapi.CMD_SickGearShowPause,
                                                  message='%s has been %spaused' % (
                                                      cur_show['name'], ('', 'un')[set_pause in (None, 0, False)]))

    def test_set_scene_numbering(self):
        self.reset_show_data = True
        self.show_save_db = sickgear.tv.TVShow.save_to_db
        sickgear.tv.TVShow.save_to_db = fake_action
        for set_scene in (1, 0):
            for cur_show in test_shows:
                params = {'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']}
                if None is not set_scene:
                    params.update({'activate': set_scene})
                data = self._request_from_api(webapi.CMD_SickGearActivateSceneNumber, params=params)
                self._check_success_base_response(data, webapi.CMD_SickGearActivateSceneNumber,
                                                  message='Scene Numbering %sactivated' % (
                                                      ('', 'de')[set_scene in (None, 0, False)]))
                r, msg = self._check_types(
                    data['data'],
                    [('indexer', integer_types), ('indexerid', integer_types), ('scenenumbering', bool),
                     ('show_name', string_types)])
                self.assertTrue(r, msg='Failed command: %s - data dict%s' % (
                    webapi._functionMaper_reversed[webapi.CMD_SickGearActivateSceneNumber], msg))
                self.assertTrue(data['data']['scenenumbering'] == bool(set_scene))
                self.assertTrue(data['data']['show_name'] == cur_show['name'])

    def test_set_show_quality(self):
        self.reset_show_data = True
        self.show_save_db = sickgear.tv.TVShow.save_to_db
        sickgear.tv.TVShow.save_to_db = fake_action
        for set_quality in [
            {'init': [Quality.SDTV],
             'upgrade': [],
             'upgrade_once': 0},
            {'init': [Quality.SDTV],
             'upgrade': [],
             'upgrade_once': 1},
            {'init': [Quality.SDTV],
             'upgrade': [Quality.FULLHDWEBDL, Quality.FULLHDBLURAY],
             'upgrade_once': 0},
            {'init': [Quality.SDTV],
             'upgrade': [Quality.FULLHDWEBDL, Quality.FULLHDBLURAY],
             'upgrade_once': 1},
            {'init': [Quality.SDTV, Quality.SDDVD, Quality.HDTV],
             'upgrade': [],
             'upgrade_once': 0},
            {'init': [Quality.SDTV, Quality.SDDVD, Quality.HDTV],
             'upgrade': [],
             'upgrade_once': 1},
            {'init': [Quality.SDTV, Quality.SDDVD, Quality.HDTV],
             'upgrade': [Quality.SDDVD, Quality.HDWEBDL],
             'upgrade_once': 0},
            {'init': [Quality.SDTV, Quality.SDDVD, Quality.HDTV],
             'upgrade': [Quality.SDDVD, Quality.HDWEBDL],
             'upgrade_once': 1},
        ]:
            for cur_show in test_shows:
                show_obj = sickgear.helpers.find_show_by_id({cur_show['tvid']: cur_show['prodid']})
                params = {'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']}
                for t in ('init', 'upgrade', 'upgrade_once'):
                    if set_quality[t]:
                        params.update({t: set_quality[t]})
                data = self._request_from_api(webapi.CMD_SickGearShowSetQuality, params=params)
                self._check_success_base_response(
                    data,
                    webapi.CMD_SickGearShowSetQuality,
                    message='%s quality has been changed to %s' % (
                        cur_show['name'], webapi._get_quality_string(show_obj.quality)))
                self.assertEqual(show_obj.upgrade_once, int(set_quality['upgrade_once']))

    def test_set_show_scene_numbers(self):
        self.reset_show_data = True
        self.show_save_db = sickgear.tv.TVShow.save_to_db
        sickgear.tv.TVShow.save_to_db = fake_action
        for set_numbers in [
            {'forSeason': 1,
             'forEpisode': 1,
             'forAbsolute': None,
             'sceneSeason': 1,
             'sceneEpisode': 2,
             'sceneAbsolute': None,
             },
        ]:
            for cur_show in test_shows:
                params = {'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid']}
                set_scene_params = params.copy()
                set_scene_params.update({'activate': 1})
                data = self._request_from_api(webapi.CMD_SickGearActivateSceneNumber,
                                              params=set_scene_params)
                self._check_success_base_response(data, webapi.CMD_SickGearActivateSceneNumber,
                                                  message='Scene Numbering activated')
                for t in ('forSeason', 'forEpisode', 'forAbsolute', 'sceneSeason', 'sceneEpisode', 'sceneAbsolute'):
                    if set_numbers[t]:
                        params.update({t: set_numbers[t]})
                data = self._request_from_api(webapi.CMD_SickGearSetSceneNumber, params=params)
                self._check_success_base_response(data, webapi.CMD_SickGearSetSceneNumber)
                self._check_types(
                    data['data'],
                    [(t, integer_types) for t in ('forSeason', 'forEpisode', 'sceneSeason', 'sceneEpisode')] +
                    [('success', bool)])
                for t in ('forSeason', 'forEpisode', 'sceneSeason', 'sceneEpisode'):
                    self.assertEqual(data['data'][t], set_numbers[t])

    def test_set_episode_status(self):
        self.org_mass_action = db.DBConnection.mass_action
        db.DBConnection.mass_action = fake_action
        self.reset_show_data = True
        failed_msg = 'Failed to set all or some status. Check data.'
        success_msg = 'All status set successfully.'
        for cur_quality_str, cur_quality in itertools.chain(iteritems(webapi.quality_map), iteritems({'None': None})):
            for cur_value, cur_status in iteritems(statusStrings.statusStrings):
                if (cur_quality and cur_value not in (SNATCHED, DOWNLOADED, ARCHIVED)) or \
                        (None is cur_quality and SNATCHED == cur_value):
                    continue
                cur_status = cur_status.lower()
                # print('Testing setting episode status to: %s %s' % (cur_status, cur_quality_str))
                if cur_value in (UNKNOWN, UNAIRED, SNATCHED_PROPER, SNATCHED_BEST, SUBTITLED):
                    continue
                for cur_show in test_shows:
                    for season, eps in iteritems(cur_show['episodes']):
                        for ep_nb, cur_ep in iteritems(eps):
                            ep_obj = sickgear.helpers.find_show_by_id({cur_show['tvid']: cur_show['prodid']}).\
                                get_episode(season, ep_nb)
                            params = {'indexer': cur_show['tvid'], 'indexerid': cur_show['prodid'], 'season': season,
                                      'episode': ep_nb, 'status': cur_status}
                            if cur_quality:
                                params.update({'quality': cur_quality_str})
                            old_status = ep_obj.status
                            status, quality = Quality.split_composite_status(ep_obj.status)
                            expect_fail = UNAIRED == status or (DOWNLOADED == status and not cur_quality)
                            expected_msg = (success_msg, failed_msg)[expect_fail]
                            data = self._request_from_api(webapi.CMD_SickGearEpisodeSetStatus, params=params)
                            r, msg = self._check_types(data,
                                                       [('result', string_types), ('data', (dict, list)[expect_fail]),
                                                        ('message', string_types)])
                            self.assertTrue(r, msg=msg)
                            self.assertTrue(data['message'].startswith(expected_msg))
                            # reset status
                            ep_obj.status = old_status
                            # ep_obj.save_to_db()


if __name__ == '__main__':
    print('==================')
    print('STARTING - WebAPI TESTS')
    print('==================')
    print('######################################################################')
    suite = unittest.TestLoader().loadTestsFromTestCase(WebAPICase)
    unittest.TextTestRunner(verbosity=2).run(suite)
