"""Offline contracts for the optional Home profile and generated slot URLs."""
from pathlib import Path
import re
import sys
import unittest
from unittest.mock import patch
from urllib.parse import parse_qs, urlsplit
import xml.etree.ElementTree as ET

sys.path.insert(0, str(Path(__file__).resolve().parent))
import stubs
stubs.install()

import xbmcaddon
from scripts.skin_harness import gen_slots
from wcs.skin import performance

ROOT = Path(__file__).resolve().parents[2]
VINYL = 'https://hosting.pilsfree.net/chudy/proxy/4017/api/vinyl/'
TMDB = 'https://image.tmdb.org/t/p/'


class PerformanceProfileTests(unittest.TestCase):
    def setUp(self):
        self.settings = {}
        self.setting_patch = patch.object(xbmcaddon.Addon, 'settings', self.settings)
        self.setting_patch.start()
        self.addCleanup(self.setting_patch.stop)

    def test_profile_is_opt_in_and_reads_setting_changes(self):
        for value, expected in [('', False), ('false', False), ('true', True), ('false', False)]:
            self.settings['skin_low_power'] = value
            self.assertEqual(performance.enabled(), expected)

    def test_normal_limits_are_preserved_and_low_power_only_caps(self):
        for count in (1, 5, 10, 20, 30, 50):
            self.settings['skin_low_power'] = 'false'
            self.assertEqual(performance.limit(count), count)
            self.settings['skin_low_power'] = 'true'
            self.assertEqual(performance.limit(count), min(count, 10))

    def test_normal_artwork_is_byte_for_byte_unchanged(self):
        for url in (None, '', 'DefaultAlbumCover.png', TMDB + 'w500/a.jpg?version=9',
                    TMDB + 'w1280/a.jpg', VINYL + 'cover/42?v=7&size=600|Authorization=Bearer%20anonymous'):
            self.assertEqual(performance.artwork(url), url)

    def test_known_tmdb_sizes_preserve_image_version_and_headers(self):
        self.settings['skin_low_power'] = 'true'
        suffix = '/a.jpg?v=7#image|User-Agent=Kodi'
        for source, target in [('w500', 'w342'), ('w780', 'w300'), ('w1280', 'w300')]:
            with self.subTest(source=source):
                self.assertEqual(performance.artwork(TMDB + source + suffix), TMDB + target + suffix)

    def test_known_vinyl_cover_routes_only_change_size(self):
        self.settings['skin_low_power'] = 'true'
        for route in ('cover/42', 'playlists/9/cover', 'browse/albums/Album%20Title/cover', 'browse/artists/cover'):
            for query in ('v=7&size=600&artist=A%2BB&albumId=4', 'size=600&v=7'):
                with self.subTest(route=route, query=query):
                    suffix = '#cover|Authorization=Bearer%20anonymous&size=600'
                    source = VINYL + route + '?' + query + suffix
                    expected = VINYL + route + '?' + query.replace('size=600', 'size=300') + suffix
                    self.assertEqual(performance.artwork(source), expected)

    def test_unknown_urls_and_smaller_sizes_are_preserved(self):
        self.settings['skin_low_power'] = 'true'
        for url in (None, '', 'DefaultAlbumCover.png', 'special://skin/media/w500/image.png',
                    TMDB + 'w185/a.jpg', TMDB + 'w342/a.jpg', TMDB + 'w300/a.jpg', TMDB + 'original/a.jpg',
                    TMDB + 'w5000/a.jpg', TMDB + 'w500',
                    'https://image.tmdb.org.example/t/p/w500/a.jpg',
                    'https://example.com/t/p/w500/a.jpg',
                    VINYL + 'stream/42?size=600', VINYL + 'other/cover?size=600',
                    VINYL + 'cover/42?size=200', VINYL + 'cover/42?size=6000',
                    VINYL + 'cover/42?size=300', VINYL + 'cover/42?v=600',
                    VINYL + 'cover/42?v=7#size=600',
                    VINYL + 'cover/42?v=7|Authorization=Bearer%20anonymous&size=600',
                    'https://example.com/cover/42?size=600'):
            with self.subTest(url=url):
                self.assertEqual(performance.artwork(url), url)

    def test_setting_defaults_off_and_has_unique_translations(self):
        setting = ET.parse(ROOT / 'resources/settings.xml').findall(".//setting[@id='skin_low_power']")
        self.assertEqual(len(setting), 1)
        self.assertEqual(setting[0].attrib, {'label': '32640', 'id': 'skin_low_power', 'type': 'bool', 'default': 'false'})
        for language in ('cs_cz', 'sk_sk', 'en_gb'):
            text = (ROOT / 'resources/language' / ('resource.language.' + language) / 'strings.po').read_text()
            entries = re.findall(r'msgctxt "#32640"\nmsgid "([^"]+)"\nmsgstr "([^"]+)"', text)
            self.assertEqual(len(entries), 1, language)
            self.assertTrue(all(entries[0]), language)

    def test_generated_slots_keep_ids_variants_options_and_clicks(self):
        generated = gen_slots.build()
        self.assertEqual(generated, (ROOT / 'resources/skin.milionar/xml/Includes_MilionarSlots.xml').read_text())
        root = ET.fromstring(generated)
        sections = root.findall('include')
        self.assertEqual(len(sections), 10 - len(gen_slots.CINEMA))          # the cinematic sections: gen_cinema.py
        self.assertEqual(len(root.findall('variable')), 54)      # every section but the first screen: gated row URLs
        list_ids = set()
        for section in sections:
            sec = section.attrib['name'].removeprefix('MilionarSlots_')
            self.assertEqual(len(section), 18)
            for n in range(1, 7):
                variants = list(section)[(n - 1) * 3:n * 3]
                self.assertEqual([v.attrib['content'] for v in variants],
                                 ['WidgetListSquare' if sec == 'vinyl' else 'WidgetListPoster',
                                  'WidgetListEpisodes', 'WidgetListCategories'])
                urls = []
                list_id = str(gen_slots.SECTIONS[sec] + n * 100)
                self.assertNotIn(list_id, list_ids)
                list_ids.add(list_id)
                for variant in variants:
                    params = {p.attrib['name']: p.attrib['value'] for p in variant}
                    self.assertEqual(params['list_id'], list_id)
                    self.assertEqual(params['browse_mode'], 'never')
                    self.assertEqual(params['onclick_condition'],
                                     '!String.IsEmpty(Container({}).ListItem.Property(milionar.exec))'.format(list_id))
                    self.assertEqual(params['onclick_action'],
                                     '$INFO[Container({}).ListItem.Property(milionar.exec)]'.format(list_id))
                    self.assertEqual(params['widget_target'], 'music' if sec == 'vinyl' else 'videos')
                    url = self.content_url(root, params['content_path'])
                    query = parse_qs(urlsplit(url).query)
                    self.assertEqual(set(query), {'action', 'row', 'sec', 'slot', 'limit', 'sort', 'style',
                                                 'click', 'hw', 'up', 'reload', 'format', 'profile'})
                    self.assertEqual(query['reload'], ['$INFO[Skin.String(mil.{}.{}.rev)]'.format(sec, n)])
                    self.assertEqual(query['format'], ['2'])
                    self.assertEqual(query['profile'], ['$INFO[Window(Home).Property(milionar.low_power)]'])
                    for option in ('limit', 'sort', 'style', 'click'):
                        self.assertEqual(query[option], ['$INFO[Skin.String(mil.{}.{}.{})]'.format(sec, n, option)])
                    self.assertNotIn('milionar.widgets.token', url)
                    urls.append(url)
                self.assertEqual(len(set(urls)), 1)
        self.assertEqual(len(list_ids), 6 * len(sections))

    @staticmethod
    def content_url(root, value):
        """The row URL: content_path is a gated skin variable; its last <value> is the URL."""
        name = value[len('$VAR['):-1]
        return root.find(".//variable[@name='{}']".format(name)).findall('value')[-1].text

    def test_revisions_are_isolated_and_profiles_have_distinct_urls(self):
        root = ET.fromstring(gen_slots.build())
        urls = [self.content_url(root, p.attrib['value']) for p in root.findall(".//param[@name='content_path']")]
        token = '$INFO[Skin.String(mil.mycinema.1.rev)]'
        changed = [url.replace(token, 'revision-2') for url in urls]
        self.assertEqual(sum(a != b for a, b in zip(urls, changed)), 3)
        self.assertEqual(len(urls), (10 - len(gen_slots.CINEMA)) * 6 * 3)
        profile = '$INFO[Window(Home).Property(milionar.low_power)]'
        for url in urls:
            normal, low = url.replace(profile, 'false'), url.replace(profile, 'true')
            self.assertNotEqual(normal, low)
            self.assertEqual(normal.replace('profile=false', 'profile=true'), low)


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