#!/usr/bin/env python3
"""Offline checks of "the user first" (wcs/skin/userfirst.py): background work waits for what the user started.

Run: python3 scripts/skin_harness/test_userfirst.py
"""
from pathlib import Path
import sys
import time
import unittest
from unittest.mock import patch

ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / 'scripts/library_harness'))
import stubs
stubs.install()
import xbmcgui
from wcs.skin import userfirst


class Monitor:
    def __init__(self):
        self.waits = 0

    def waitForAbort(self, seconds):
        self.waits += 1
        return False


class UserFirstTests(unittest.TestCase):
    def setUp(self):
        xbmcgui.Window.props.clear()

    def test_a_fresh_mark_makes_the_background_wait_and_an_old_one_does_not(self):
        self.assertFalse(userfirst.active())                               # nothing was started yet
        userfirst.mark()
        self.assertTrue(userfirst.active())
        with patch.object(time, 'time', return_value=time.time() + userfirst.HOLD + 0.1):
            self.assertFalse(userfirst.active())
        xbmcgui.Window(10000).setProperty(userfirst.PROPERTY, 'not a number')
        self.assertFalse(userfirst.active())

    def test_the_wait_is_bounded_and_ends_when_the_action_is_no_longer_fresh(self):
        userfirst.mark()
        monitor = Monitor()
        self.assertFalse(userfirst.give_way(monitor, limit=0.5))            # never longer than the limit
        self.assertEqual(monitor.waits, 5)
        xbmcgui.Window.props.clear()
        monitor = Monitor()
        userfirst.give_way(monitor)
        self.assertEqual(monitor.waits, 0)

    def test_a_longer_job_steps_aside_only_when_the_service_runs_it(self):
        import threading
        userfirst.mark()
        with patch.object(userfirst, 'give_way') as waited:
            userfirst.breathe()                                            # the user's own run: never waits for itself
            waited.assert_not_called()
            done = []

            def job():
                userfirst.background_thread()
                userfirst.breathe()
                done.append(waited.call_count)
            thread = threading.Thread(target=job)
            thread.start()
            thread.join()
        self.assertEqual(done, [1])
        for path in ('wcs/foryou/engine.py', 'wcs/library2/tmdb.py'):       # the rebuild of Pro tebe, a show's metadata
            self.assertIn('userfirst.breathe()', (ROOT / path).read_text(encoding='utf-8'), path)
        for path in ('wcs/foryou/service.py', 'wcs/skin/service.py'):
            self.assertIn('userfirst.background_thread()', (ROOT / path).read_text(encoding='utf-8'), path)

    def test_every_plugin_run_but_a_home_row_is_marked_and_the_background_loops_step_aside(self):
        main = (ROOT / 'main.py').read_text(encoding='utf-8')
        self.assertIn("if not _action.startswith('widget_') and _action != 'skin_detail_row'", main)
        self.assertLess(main.index('userfirst.mark()'), main.index('_manage_modules()'))     # before anything is imported
        for name in ('journal.py', 'refresh.py', 'service.py', 'bytecode.py'):               # prepared rows, publication, cache builds
            self.assertIn('userfirst.give_way(', (ROOT / 'wcs/skin' / name).read_text(encoding='utf-8'), name)
        self.assertIn('userfirst.active()', (ROOT / 'wcs/skin/warm.py').read_text(encoding='utf-8'))
        self.assertIn('userfirst.mark()', (ROOT / 'wcs/skin/commands.py').read_text(encoding='utf-8'))


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