# -*- coding: utf-8 -*-
"""L4 unit tests: Home rows of My movies / My series / Continue (docs/plan/milionar-library/070 §3, 120 §2)."""
import os
import sys
import tempfile
import unittest
from datetime import date, timedelta

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import stubs  # noqa: E402

stubs.install()

import xbmcaddon  # noqa: E402
import xbmcplugin  # noqa: E402

from wcs.library2 import listings, meta, rows, store, urls  # noqa: E402

TODAY = date(2026, 6, 15)


def iso(days):
    return (TODAY + timedelta(days=days)).isoformat()


class RowTests(unittest.TestCase):
    def setUp(self):
        store.set_path(os.path.join(tempfile.mkdtemp(prefix='milionar-db-'), 'library.db'))
        for table in (xbmcplugin.ITEMS, xbmcplugin.ENDED, xbmcplugin.CONTENT, xbmcplugin.SORTS):
            table.clear()
        xbmcaddon.Addon.settings.clear()
        self._date = rows.date
        rows.date = type('D', (), {'today': staticmethod(lambda: TODAY)})
        self._ldate = listings.date
        listings.date = rows.date

    def tearDown(self):
        rows.date, listings.date = self._date, self._ldate

    # ------------------------------------------------------------ fixtures
    def movie(self, tmdb_id, title, in_list=True, genres=((18, 'Drama'),), added=None, **extra):
        meta.put(meta.key_movie(tmdb_id), dict({'id': tmdb_id, 'title': title, 'release_date': '2020-01-01',
                                                'vote_average': 7.0, 'poster_path': '/p{}.jpg'.format(tmdb_id),
                                                'genres': [{'id': g, 'name': n} for g, n in genres]}, **extra), 10 ** 9)
        if in_list:
            store.set_in_list('movie', tmdb_id, True, {'id': tmdb_id, 'title': title})

    def show(self, tmdb_id, name, episodes_by_season, in_list=True, status='Returning Series', genres=((18, 'Drama'),)):
        meta.put(meta.key_tv(tmdb_id), {'id': tmdb_id, 'name': name, 'status': status, 'first_air_date': '2020-01-01',
                                        'poster_path': '/s{}.jpg'.format(tmdb_id), 'backdrop_path': '/b{}.jpg'.format(tmdb_id),
                                        'genres': [{'id': g, 'name': n} for g, n in genres],
                                        'seasons': [{'season_number': n} for n in episodes_by_season]}, 10 ** 9)
        for number, days in episodes_by_season.items():
            meta.put(meta.key_season(tmdb_id, number), {'episodes': [
                {'episode_number': i + 1, 'name': 'Ep {}'.format(i + 1), 'air_date': iso(d)} for i, d in enumerate(days)]}, 10 ** 9)
        if in_list:
            store.set_in_list('tv', tmdb_id, True, {'id': tmdb_id, 'name': name})

    def rows(self, row, **params):
        return rows.build(row, dict(params, row=row))

    def titles(self, items):
        return [i.label for _p, i, _f in items]

    # ------------------------------------------------------------ movies
    def test_continue_movies_in_progress_newest_first_hidden_and_watched_excluded(self):
        for n, title in ((1, 'A'), (2, 'B'), (3, 'C'), (4, 'D'), (5, 'E')):
            self.movie(n, title, in_list=(n != 5))
        store.save_resume('movie', 1, 600, 6000, when=1000)
        store.save_resume('movie', 2, 700, 6000, when=3000)
        store.save_resume('movie', 5, 800, 6000, when=2000)                  # not in My movies, still in Continue
        store.save_resume('movie', 3, 900, 6000, when=4000)
        store.set_hidden_from_continue('movie', 3, True, when=5000)
        store.mark_watched('movie', 4)
        items = self.rows('cont_movies')
        self.assertEqual(self.titles(items), ['B', 'E', 'A'])
        path, item, folder = items[0]
        self.assertIn('mplay_req', path.replace('%3D', '=') + path)
        self.assertTrue('src%3Dknown' in path and 'from%3Dresume' in path)             # the same stream as last time, else the dialog
        self.assertEqual((item.tag.values['ResumePoint'], folder), ((700.0, 6000.0), False))
        self.assertIn('landscape', item.art)

    def test_my_movies_rows(self):
        for n, title in ((1, 'Alfa'), (2, 'Beta'), (3, 'Gama'), (4, 'Delta')):
            self.movie(n, title)
        store.save_resume('movie', 1, 600, 6000, when=100)
        store.mark_watched('movie', 2, when=200)
        self.assertEqual(self.titles(self.rows('my_movies_progress')), ['Alfa'])
        self.assertEqual(self.titles(self.rows('my_movies_watched')), ['Beta'])
        self.assertEqual(sorted(self.titles(self.rows('my_movies_unwatched'))), ['Delta', 'Gama'])
        self.assertEqual(self.titles(self.rows('my_movies_all')), ['Alfa', 'Beta', 'Delta', 'Gama'])           # title
        self.assertEqual(self.titles(self.rows('my_movies_all', hw='1')), ['Alfa', 'Delta', 'Gama'])           # hide watched
        self.assertEqual(len(self.rows('my_movies_all', limit='2')), 2)
        browse = self.items_playable(self.rows('my_movies_all'))[0]                     # browse rows open the details dialog
        self.assertTrue('show_wcs_details' in browse and 'tmdb_id%3D1' in browse)
        store.save_resume('movie', 4, 600, 6000)
        self.assertIn('mplay_req', self.items_playable(self.rows('my_movies_progress'))[0])      # in progress: resume
        self.assertEqual(len(self.rows('my_movies_all', sort='random')), 4)

    def test_movie_items_carry_the_hero_properties(self):
        """190-cinema: the hero of the cinematic Home is drawn from properties of the selected item."""
        xbmcaddon.Addon.strings.update({32549: '{} min left', 32550: '{}h {:02d}m left'})
        self.movie(1, 'Inside Out', genres=((16, 'Animovaný'), (18, 'Drama')), runtime=95, backdrop_path='/wide.jpg',
                   overview='word ' * 100)
        self.movie(2, 'A very long title of a film that needs the small font')
        store.save_resume('movie', 1, 4320, 5700, when=100)
        item = self.rows('my_movies_progress')[0][1]
        prop = lambda key: item.getProperty('milionar.hero.' + key)  # noqa: E731
        self.assertEqual(prop('kicker').replace('\u200a', ''), 'ANIMOVANÝ · 2020 · 1h 35m')
        self.assertEqual((prop('size'), prop('left'), prop('rating')), ('l', '23 min left', '7.0'))
        self.assertEqual(prop('fanart'), 'https://image.tmdb.org/t/p/original/wide.jpg')
        self.assertTrue(prop('plot').endswith('…') and len(prop('plot')) <= 191)
        self.assertTrue(prop('play').startswith('RunPlugin(') and 'mplay_req' in prop('play') and 'from=resume' in prop('play'))
        self.assertTrue('show_wcs_details' in prop('detail') and 'tmdb_id=1' in prop('detail'))
        other = {i.label: i for _p, i, _f in self.rows('my_movies_all')}['A very long title of a film that needs the small font']
        self.assertEqual((other.getProperty('milionar.hero.size'), other.getProperty('milionar.hero.left')), ('s', ''))
        self.assertEqual(self.rows('my_movies_progress')[0][1].getProperty('milionar.hero.left'), '23 min left')   # cached snapshot

    def test_series_and_episode_items_carry_the_hero_properties(self):
        """190-cinema: a series shows seasons and years, an episode its series, its code and the time left."""
        xbmcaddon.Addon.strings.update({32549: '{} min left', 32551: '{} season', 32552: '{} seasons (few)', 32553: '{} seasons'})
        self.show(1, 'Friends', {1: [-30, -20, -10]}, status='Ended', genres=((35, 'Komedie'),))
        self.show(2, 'Running', {1: [-30, 10]})
        for tmdb_id, extra in ((1, {'number_of_seasons': 10, 'last_air_date': '2024-05-06', 'vote_average': 8.44}), (2, {'number_of_seasons': 1})):
            data, _stale = meta.get(meta.key_tv(tmdb_id))
            meta.put(meta.key_tv(tmdb_id), dict(data, **extra), 10 ** 9)
        store.save_resume('episode', 1, 600, 1320, season=1, episode=2, when=100)
        store.mark_watched('episode', 2, season=1, episode=1, when=200)
        prop = lambda item, key: item.getProperty('milionar.hero.' + key).replace('\u200a', '')  # noqa: E731
        shows = {i.label: i for _p, i, _f in self.rows('my_series_all')}
        self.assertEqual(prop(shows['Friends'], 'kicker'), 'KOMEDIE · 10 seasons · 2020–2024')
        self.assertEqual(prop(shows['Running'], 'kicker'), 'DRAMA · 1 season · 2020–')
        self.assertEqual((prop(shows['Friends'], 'title'), prop(shows['Friends'], 'left'), prop(shows['Friends'], 'note')),
                         ('Friends', '', ''))
        self.assertTrue('ctx_next' in prop(shows['Friends'], 'play') and 'media_type=tv' in prop(shows['Friends'], 'detail'))
        episodes = {prop(i, 'title'): i for _p, i, _f in self.rows('my_series_next')}
        self.assertEqual((prop(episodes['Friends'], 'left'), prop(episodes['Friends'], 'note')), ('S1 · E2 · 12 min left', 'S1 · E2'))
        self.assertTrue('mplay_req' in prop(episodes['Friends'], 'play') and 's=1&e=2' in prop(episodes['Friends'], 'play'))
        self.assertEqual(prop(episodes['Friends'], 'fanart'), 'https://image.tmdb.org/t/p/original/b1.jpg')
        self.assertEqual((prop(episodes['Friends'], 'rating'), prop(shows['Running'], 'rating')), ('8.4', ''))
        upcoming = episodes['Running']                                       # not out yet: the air date instead of the time
        self.assertTrue(prop(upcoming, 'left').startswith('S1 · E2 · vyjde') and 'mupcoming' in prop(upcoming, 'play'))

    def items_playable(self, items):
        return [p for p, _i, _f in items]

    def test_landscape_style_puts_the_wide_image_into_the_poster_slot(self):
        self.movie(1, 'A', backdrop_path='/wide.jpg')
        poster = self.rows('my_movies_all', style='poster')[0][1].art
        wide = self.rows('my_movies_all', style='landscape')[0][1].art
        self.assertEqual((poster['poster'], wide['poster']), ('https://image.tmdb.org/t/p/w500/p1.jpg', 'https://image.tmdb.org/t/p/w780/wide.jpg'))
        self.assertEqual(self.rows('my_movies_progress')[0:0], [])

    def test_my_movies_sorts(self):
        self.movie(1, 'Zed', release_date='2010-05-05', vote_average=5.0)
        self.movie(2, 'Abe', release_date='2022-05-05', vote_average=9.0)
        self.assertEqual(self.titles(self.rows('my_movies_all', sort='year')), ['Abe', 'Zed'])
        self.assertEqual(self.titles(self.rows('my_movies_all', sort='rating')), ['Abe', 'Zed'])
        self.assertEqual(self.titles(self.rows('my_movies_all', sort='bogus')), ['Abe', 'Zed'])               # default title

    def test_movie_genre_row_counts_and_opens_the_filtered_listing(self):
        self.movie(1, 'A', genres=((18, 'Drama'), (35, 'Komedie')))
        self.movie(2, 'B', genres=((18, 'Drama'),))
        items = self.rows('my_movies_genres')
        self.assertEqual(self.titles(items), ['Drama (2)', 'Komedie (1)'])
        self.assertTrue(items[0][0].endswith('action=mlist&kind=movies&genre=18&skin=1'))
        self.assertTrue(items[0][2])
        listings.run(1, {'kind': 'movies', 'genre': '35'})
        self.assertEqual([i.label for _p, i, _f in xbmcplugin.ITEMS[1] if 'SpecialSort' not in i.props], ['A'])
        self.assertEqual([i for _p, i, _f in xbmcplugin.ITEMS[1] if 'SpecialSort' in i.props], [])            # no buttons

    # ------------------------------------------------------------ series
    def test_continue_series_resume_next_upcoming_and_the_upcoming_switch(self):
        self.show(10, 'Resumed', {1: [-30, -20, -10]})
        self.show(11, 'Nexting', {1: [-30, -20, -10]})
        self.show(12, 'Waiting', {1: [-30, -20, 5]})
        self.show(13, 'Fresh', {1: [-30]})
        store.save_resume('tv', 10, 300, 2400, 1, 2, when=500)
        store.mark_watched('tv', 11, 1, 1, when=400)
        store.mark_watched('tv', 12, 1, 2, when=300)
        items = self.rows('cont_series')
        self.assertEqual([(i.tag.values['TvShowTitle'], i.tag.values['Season'], i.tag.values['Episode'], i.label) for _p, i, _f in items],
                         [('Resumed', 1, 2, 'Ep 2'), ('Nexting', 1, 2, 'Ep 2'), ('Waiting', 1, 3, 'vyjde 20. 6.')])
        self.assertTrue('mplay_req' in items[0][0] and 'from%3Dresume' in items[0][0])
        self.assertTrue('from%3Dresume' in items[1][0])                      # no saved position: it starts from 0 anyway
        self.assertTrue('mupcoming' in items[2][0])                          # unaired: only the date
        self.assertEqual(self.titles(self.rows('cont_series', up='0')), ['Ep 2', 'Ep 2'])

    def test_my_series_rows(self):
        self.show(10, 'Aaa', {1: [-60, -50, -3]})
        self.show(11, 'Bbb', {1: [-60, -50, -40]})
        self.show(12, 'Ccc', {1: [-60, 4, 9]}, genres=((10765, 'Sci-Fi'),))
        store.mark_watched('tv', 10, 1, 1)
        store.mark_watched('tv', 10, 1, 2)
        for e in (1, 2, 3):
            store.mark_watched('tv', 11, 1, e)
        # new: aired within 14 days and unwatched
        new = self.rows('my_series_new')
        self.assertEqual([(i.tag.values['TvShowTitle'], i.tag.values['Episode']) for _p, i, _f in new], [('Aaa', 3)])
        # upcoming: next unaired episode, earliest first
        up = self.rows('my_series_upcoming')
        self.assertEqual([(i.tag.values['TvShowTitle'], i.label) for _p, i, _f in up], [('Ccc', 'vyjde 19. 6.')])
        # next: started series only
        self.assertEqual([i.tag.values['TvShowTitle'] for _p, i, _f in self.rows('my_series_next')], ['Aaa'])
        self.assertEqual(self.titles(self.rows('my_series_unwatched')), ['Ccc'])
        self.assertEqual(self.titles(self.rows('my_series_all')), ['Aaa', 'Bbb', 'Ccc'])
        self.assertEqual(self.titles(self.rows('my_series_all', hw='1')), ['Aaa', 'Ccc'])                     # Bbb fully watched
        path, item, folder = self.rows('my_series_all')[0]
        self.assertEqual((item.props['TotalEpisodes'], item.props['WatchedEpisodes'], item.props['UnWatchedEpisodes']), ('3', '2', '1'))
        self.assertEqual(item.tag.values['Playcount'], 0)
        self.assertEqual(self.rows('my_series_all')[1][1].tag.values['Playcount'], 1)          # Bbb: all watched
        self.assertTrue(path.endswith('action=mlist&kind=seasons&tmdb=10') and folder)
        self.assertEqual([i.label for _p, i, _f in self.rows('my_series_genres')], ['Drama (2)', 'Sci-Fi (1)'])

    def test_series_without_cached_data_is_still_listed_from_the_snapshot(self):
        store.set_in_list('tv', 77, True, {'id': 77, 'name': 'Snapshot', 'poster_path': '/x.jpg'})
        item = self.rows('my_series_all')[0][1]
        self.assertEqual((item.label, item.art['poster']), ('Snapshot', 'https://image.tmdb.org/t/p/w500/x.jpg'))
        self.assertEqual(self.rows('my_series_next'), [])

    # ------------------------------------------------------------ recent and delegation
    def test_recent_played_distinct_newest_first(self):
        self.movie(1, 'Film')
        self.show(10, 'Serial', {1: [-30, -20]})
        store.log_play('movie', 1, None, None, 100, 200, 600, 6000, 'resume')
        store.log_play('tv', 10, 1, 1, 300, 400, 600, 2400, 'watched')
        store.log_play('movie', 1, None, None, 500, 600, 600, 6000, 'resume')
        store.log_play('tv', 10, 1, 2, 700, 800, 600, 2400, 'watched')
        items = self.rows('recent_played')
        self.assertEqual([i.tag.values['MediaType'] for _p, i, _f in items], ['episode', 'movie'])
        self.assertEqual((items[0][1].tag.values['Season'], items[0][1].tag.values['Episode']), (1, 2))

    def test_search_rows_match_titles_without_accents_or_case(self):
        self.movie(1, 'Černobyl 1986')
        self.movie(2, 'Jiný film')
        self.show(10, 'Příběh hraček', {1: [-30]})
        self.assertEqual(self.titles(self.rows('search_my_movies', q='cernobyl')), ['Černobyl 1986'])
        self.assertEqual(self.titles(self.rows('search_my_movies', q='FILM')), ['Jiný film'])
        self.assertEqual(self.titles(self.rows('search_my_series', q='pribeh')), ['Příběh hraček'])
        self.assertEqual(self.rows('search_my_movies', q=''), [])
        self.assertEqual(self.rows('search_my_series', q='zzz'), [])
        rows.run(7, {'row': 'search_my_movies', 'q': 'film'})                       # reachable through run()
        self.assertEqual(len(xbmcplugin.ITEMS[7]), 1)

    def test_game_picker_and_row_use_the_lists_not_the_kodi_library(self):
        from wcs.skin import actions
        self.movie(1, 'Zebra')
        self.movie(2, 'Alfa')
        self.show(10, 'Seriál', {1: [-30]})
        found = actions._library_titles('movie')
        self.assertEqual([r['title'] for r in found], ['Alfa', 'Zebra'])
        self.assertEqual((found[0]['uniqueid']['tmdb'], found[0]['media_type']), ('2', 'movie'))
        self.assertEqual(actions._library_titles('tvshow')[0]['media_type'], 'tv')
        self.assertEqual(len(actions._random_library_entries_from_lists(5)) if hasattr(actions, '_random_library_entries_from_lists') else 3, 3)
        from wcs.skin import widgets
        self.assertEqual(len(widgets._random_library_entries(10)), 3)                 # 2 movies + 1 series, no RPC

    def test_unknown_catalogue_rows_fall_through_to_the_old_widgets(self):
        rows.run(3, {'row': 'widget_csfd_charts'})
        self.assertEqual(len(xbmcplugin.ITEMS[3]), 4)
        rows.run(4, {'row': 'nonsense'})
        self.assertEqual(xbmcplugin.ENDED[4]['succeeded'], True)

    def test_a_broken_row_still_ends_the_directory(self):
        original = rows.build
        rows.build = lambda row, params: 1 / 0
        try:
            rows.run(5, {'row': 'my_movies_all'})
        finally:
            rows.build = original
        self.assertEqual(xbmcplugin.ENDED[5]['succeeded'], True)


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