#!/usr/bin/env python3
"""Offline checks of the bytecode the service makes on the device (wcs/skin/bytecode.py) and the release ships.

Run: python3 scripts/skin_harness/test_bytecode.py
"""
import importlib.util
from pathlib import Path
import sys
import tempfile
import unittest

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


class BytecodeTests(unittest.TestCase):
    def setUp(self):
        folder = tempfile.TemporaryDirectory()
        self.addCleanup(folder.cleanup)
        self.root = Path(folder.name)
        (self.root / 'pkg').mkdir()
        (self.root / 'pkg' / '__init__.py').write_text('')
        (self.root / 'pkg' / 'one.py').write_text('VALUE = 1\n')
        (self.root / 'pkg' / 'broken.py').write_text('def (:\n')

    def cached(self, name):
        return importlib.util.cache_from_source(str(self.root / 'pkg' / name))

    def test_missing_bytecode_is_made_once_and_broken_code_is_left_alone(self):
        self.assertEqual(bytecode.compile_tree(str(self.root)), (2, 0, 1))
        self.assertTrue(bytecode.valid(str(self.root / 'pkg' / 'one.py'), self.cached('one.py')))
        self.assertFalse(Path(self.cached('broken.py')).exists())
        self.assertEqual(bytecode.compile_tree(str(self.root)), (0, 2, 1))          # nothing is written twice

    def test_bytecode_that_does_not_belong_to_its_source_is_replaced(self):
        bytecode.compile_tree(str(self.root))
        (self.root / 'pkg' / 'one.py').write_text('VALUE = 2\n')
        self.assertFalse(bytecode.valid(str(self.root / 'pkg' / 'one.py'), self.cached('one.py')))
        self.assertEqual(bytecode.compile_tree(str(self.root)), (1, 1, 1))
        spec = importlib.util.spec_from_file_location('one', self.root / 'pkg' / 'one.py')
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        self.assertEqual(module.VALUE, 2)

    def test_the_files_are_hash_checked_never_time_stamped(self):
        bytecode.compile_tree(str(self.root))
        with open(self.cached('one.py'), 'rb') as handle:
            header = handle.read(16)
        self.assertEqual(header[4:8], bytecode.CHECKED)
        self.assertEqual(header[8:16], importlib.util.source_hash((self.root / 'pkg' / 'one.py').read_bytes()))

    def test_one_file_and_a_folder_that_cannot_be_written(self):
        self.assertEqual(bytecode.compile_tree(str(self.root / 'pkg' / 'one.py')), (1, 0, 0))
        read_only = self.root / 'locked'
        read_only.mkdir()
        (read_only / 'two.py').write_text('VALUE = 2\n')
        read_only.chmod(0o555)
        self.addCleanup(read_only.chmod, 0o755)
        self.assertEqual(bytecode.compile_tree(str(read_only)), (0, 0, 1))

    def test_the_release_ships_the_same_kind_for_the_python_of_kodi_21(self):
        build = (ROOT / 'scripts/build_zip.sh').read_text(encoding='utf-8')
        # Kodi runs Python with -O and therefore looks for <module>.cpython-311.opt-1.pyc
        self.assertIn("BYTECODE_PYTHON, BYTECODE_TAG = 'python3.11', 'cpython-311.opt-1'", build)
        self.assertIn('optimize=1', build)
        self.assertIn('PycInvalidationMode.CHECKED_HASH', build)
        self.assertIn("relative not in ('main.py', 'service.py', 'context.py')", build)


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