#!/usr/bin/env python3
"""Offline checks of the update window's logic (wcs/skin/update.py, docs/plan/skin-milionar/250-update.md).

Run: python3 scripts/skin_harness/test_update.py
"""
import importlib.util
import json
import os
import sys
import tempfile
import types
import unittest
from unittest.mock import Mock, patch

ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
STRINGS = {32766: 'Milionář {} is up to date.', 32768: 'Milionář {} is installed.'}


class UpdateTests(unittest.TestCase):
    def setUp(self):
        folder = tempfile.TemporaryDirectory()
        self.addCleanup(folder.cleanup)
        self.addon_path = os.path.join(folder.name, 'plugin.video.milionar')
        os.makedirs(self.addon_path)
        self.props, self.builtins, self.version = {}, [], '2.1.76'
        self.settings = {'general.addonupdates': 0}
        self.repo_installed, self.active = True, set()
        modules = {name: types.ModuleType(name) for name in ('xbmc', 'xbmcaddon', 'xbmcgui', 'xbmcvfs')}
        xbmc = modules['xbmc']
        xbmc.LOGINFO, xbmc.LOGWARNING, xbmc.LOGERROR = 1, 2, 3
        xbmc.log = Mock()
        xbmc.executebuiltin = self.builtins.append
        xbmc.getCondVisibility = lambda condition: condition in self.active
        monitor = Mock()
        monitor.waitForAbort.return_value = True
        xbmc.Monitor = Mock(return_value=monitor)

        def rpc(request):
            request = json.loads(request)
            if request['method'] == 'Settings.GetSettingValue':
                return json.dumps({'result': {'value': self.settings[request['params']['setting']]}})
            return json.dumps({'result': {'addon': {'enabled': True}} if self.repo_installed else {}})

        xbmc.executeJSONRPC = rpc
        addon = Mock()
        addon.getAddonInfo.side_effect = lambda key: self.addon_path if key == 'path' else self.version
        addon.getLocalizedString.side_effect = lambda string_id: STRINGS.get(string_id, 'S{}'.format(string_id))
        modules['xbmcaddon'].Addon = Mock(return_value=addon)
        window = Mock()
        window.getProperty.side_effect = lambda key: self.props.get(key, '')
        window.setProperty.side_effect = self.props.__setitem__
        window.clearProperty.side_effect = lambda key: self.props.pop(key, None)
        modules['xbmcgui'].Window = Mock(return_value=window)
        modules['xbmcvfs'].translatePath = lambda path: path
        with patch.dict(sys.modules, modules):
            spec = importlib.util.spec_from_file_location('update_under_test', os.path.join(ROOT, 'wcs/skin/update.py'))
            self.update = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(self.update)

    def prop(self, name):
        return self.props.get('milionar.update.' + name, '')

    def published(self, version, news=''):
        return patch.object(self.update, 'published', return_value=(version, news))

    def test_versions_compare_as_numbers(self):
        self.assertGreater(self.update.version_key('2.1.100'), self.update.version_key('2.1.99'))

    def test_new_version_offers_install_with_its_news(self):
        with self.published('2.1.77', 'Hero buttons fit their text'):
            self.update.check()
        self.assertEqual((self.prop('state'), self.prop('installed'), self.prop('available')), ('available', '2.1.76', '2.1.77'))
        self.assertEqual((self.prop('next'), self.prop('text')), ('install', 'Hero buttons fit their text'))
        self.assertTrue(self.prop('primary') and self.prop('secondary'))

    def test_the_main_button_gets_the_focus_when_it_appears(self):
        self.active = {'Window.IsActive(1124)'}
        with self.published('2.1.77'):
            self.update.check()
        self.assertEqual(self.builtins, ['SetFocus(11)'])

    def test_same_version_is_current(self):
        with self.published('2.1.76'):
            self.update.check()
        self.assertEqual((self.prop('state'), self.prop('primary'), self.prop('text')),
                         ('current', '', 'Milionář 2.1.76 is up to date.'))

    def test_network_error_offers_retry(self):
        with patch.object(self.update, 'published', side_effect=OSError('offline')):
            self.update.check()
        self.assertEqual((self.prop('state'), self.prop('next')), ('failed', 'retry'))

    def test_workspace_never_offers_nor_starts_an_install(self):
        os.makedirs(os.path.join(self.addon_path, '.git'))
        with self.published('2.1.77'):
            self.update.check()
        self.assertEqual((self.prop('state'), self.prop('primary'), self.prop('next')), ('available', '', ''))
        self.props['milionar.update.next'] = 'install'                 # a stale click
        self.update.run({'do': 'primary'})
        self.assertNotIn('UpdateAddonRepos', self.builtins)
        self.assertEqual(self.builtins, [])

    def test_without_the_repository_there_is_no_install(self):
        self.repo_installed = False
        with self.published('2.1.77'):
            self.update.check()
        self.assertEqual(self.prop('next'), '')

    def test_install_asks_kodi_and_remembers_the_target(self):
        with self.published('2.1.77'):
            self.update.check()
        self.update.run({'do': 'primary'})
        self.assertEqual(self.builtins, ['UpdateAddonRepos'])
        self.assertEqual((self.prop('state'), self.prop('target'), self.prop('busy')), ('installing', '2.1.77', 'true'))

    def test_without_automatic_updates_kodi_list_of_updates_opens(self):
        self.settings['general.addonupdates'] = 1
        with self.published('2.1.77'):
            self.update.check()
        self.update.run({'do': 'primary'})
        self.assertEqual(self.builtins, ['UpdateAddonRepos', 'Dialog.Close(1124)', self.update.UPDATES_LIST])

    def test_new_service_finishes_the_window(self):
        self.props.update({'milionar.update.state': 'installing', 'milionar.update.target': '2.1.77'})
        self.version = '2.1.77'
        self.active = {'Window.IsActive(home)'}
        self.update.resume()
        self.assertEqual((self.prop('state'), self.prop('text'), self.prop('target')),
                         ('done', 'Milionář 2.1.77 is installed.', ''))
        self.assertEqual(self.builtins, ['ActivateWindow(1124)'])

    def test_service_start_without_the_update_forgets_it(self):
        self.props.update({'milionar.update.state': 'installing', 'milionar.update.target': '2.1.77'})
        self.update.resume()
        self.assertEqual((self.prop('state'), self.prop('target'), self.builtins), ('', '', []))


if __name__ == '__main__':
    unittest.main(verbosity=1)
