import functools

from .common import InfoExtractor
from ..utils import str_or_none, url_or_none
from ..utils.traversal import traverse_obj


class AsobiStageIE(InfoExtractor):
    IE_DESC = 'ASOBISTAGE (アソビステージ)'
    _VALID_URL = r'https?://asobistage\.asobistore\.jp/event/(?P<id>(?P<event>\w+)/(?P<type>archive|player)/(?P<slug>\w+))(?:[?#]|$)'
    _TESTS = [{
        'url': 'https://asobistage.asobistore.jp/event/315passionhour_2022summer/archive/frame',
        'info_dict': {
            'id': '315passionhour_2022summer/archive/frame',
            'title': '315プロダクションプレゼンツ 315パッションアワー!!!',
            'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
        },
        'playlist_count': 1,
        'playlist': [{
            'info_dict': {
                'id': 'edff52f2',
                'ext': 'mp4',
                'title': '315passion_FRAME_only',
                'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
            },
        }],
    }, {
        'url': 'https://asobistage.asobistore.jp/event/idolmaster_idolworld2023_goods/archive/live',
        'info_dict': {
            'id': 'idolmaster_idolworld2023_goods/archive/live',
            'title': 'md5:378510b6e830129d505885908bd6c576',
            'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
        },
        'playlist_count': 1,
        'playlist': [{
            'info_dict': {
                'id': '3aef7110',
                'ext': 'mp4',
                'title': 'asobistore_station_1020_serverREC',
                'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
            },
        }],
    }, {
        'url': 'https://asobistage.asobistore.jp/event/sidem_fclive_bpct/archive/premium_hc',
        'playlist_count': 4,
        'info_dict': {
            'id': 'sidem_fclive_bpct/archive/premium_hc',
            'title': '315 Production presents F＠NTASTIC COMBINATION LIVE ～BRAINPOWER!!～/～CONNECTIME!!!!～',
            'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
        },
    }, {
        'url': 'https://asobistage.asobistore.jp/event/ijigenfes_utagassen/player/day1',
        'only_matching': True,
    }]

    _API_HOST = 'https://asobistage-api.asobistore.jp'
    _HEADERS = {}
    _is_logged_in = False

    @functools.cached_property
    def _owned_tickets(self):
        owned_tickets = set()
        if not self._is_logged_in:
            return owned_tickets

        for path, name in [
            ('api/v1/purchase_history/list', 'ticket purchase history'),
            ('api/v1/serialcode/list', 'redemption history'),
        ]:
            response = self._download_json(
                f'{self._API_HOST}/{path}', None, f'Downloading {name}',
                f'Unable to download {name}', expected_status=400)
            if traverse_obj(response, ('payload', 'error_message'), 'error') == 'notlogin':
                self._is_logged_in = False
                break
            owned_tickets.update(
                traverse_obj(response, ('payload', 'value', ..., 'digital_product_id', {str_or_none})))

        return owned_tickets

    def _get_available_channel_id(self, channel):
        channel_id = traverse_obj(channel, ('chennel_vspf_id', {str}))
        if not channel_id:
            return None
        # if rights_type_id == 6, then 'No conditions (no login required - non-members are OK)'
        if traverse_obj(channel, ('viewrights', lambda _, v: v['rights_type_id'] == 6)):
            return channel_id
        available_tickets = traverse_obj(channel, (
            'viewrights', ..., ('tickets', 'serialcodes'), ..., 'digital_product_id', {str_or_none}))
        if not self._owned_tickets.intersection(available_tickets):
            self.report_warning(
                f'You are not a ticketholder for "{channel.get("channel_name") or channel_id}"')
            return None
        return channel_id

    def _real_initialize(self):
        if self._get_cookies(self._API_HOST):
            self._is_logged_in = True
        token = self._download_json(
            f'{self._API_HOST}/api/v1/vspf/token', None, 'Getting token', 'Unable to get token')
        self._HEADERS['Authorization'] = f'Bearer {token}'

    def _real_extract(self, url):
        video_id, event, type_, slug = self._match_valid_url(url).group('id', 'event', 'type', 'slug')
        video_type = {'archive': 'archives', 'player': 'broadcasts'}[type_]
        webpage = self._download_webpage(url, video_id)
        event_data = traverse_obj(
            self._search_nextjs_data(webpage, video_id, default={}),
            ('props', 'pageProps', 'eventCMSData', {
                'title': ('event_name', {str}),
                'thumbnail': ('event_thumbnail_image', {url_or_none}),
            }))

        available_channels = traverse_obj(self._download_json(
            f'https://asobistage.asobistore.jp/cdn/v101/events/{event}/{video_type}.json',
            video_id, 'Getting channel list', 'Unable to get channel list'), (
            video_type, lambda _, v: v['broadcast_slug'] == slug,
            'channels', lambda _, v: v['chennel_vspf_id'] != '00000'))

        entries = []
        for channel_id in traverse_obj(available_channels, (..., {self._get_available_channel_id})):
            if video_type == 'archives':
                channel_json = self._download_json(
                    f'https://survapi.channel.or.jp/proxy/v1/contents/{channel_id}/get_by_cuid', channel_id,
                    'Getting archive channel info', 'Unable to get archive channel info', fatal=False,
                    headers=self._HEADERS)
                channel_data = traverse_obj(channel_json, ('ex_content', {
                    'm3u8_url': 'streaming_url',
                    'title': 'title',
                    'thumbnail': ('thumbnail', 'url'),
                }))
            else:  # video_type == 'broadcasts'
                channel_json = self._download_json(
                    f'https://survapi.channel.or.jp/ex/events/{channel_id}', channel_id,
                    'Getting live channel info', 'Unable to get live channel info', fatal=False,
                    headers=self._HEADERS, query={'embed': 'channel'})
                channel_data = traverse_obj(channel_json, ('data', {
                    'm3u8_url': ('Channel', 'Custom_live_url'),
                    'title': 'Name',
                    'thumbnail': 'Poster_url',
                }))

            entries.append({
                'id': channel_id,
                'title': channel_data.get('title'),
                'formats': self._extract_m3u8_formats(channel_data.get('m3u8_url'), channel_id, fatal=False),
                'is_live': video_type == 'broadcasts',
                'thumbnail': url_or_none(channel_data.get('thumbnail')),
            })

        if not self._is_logged_in and not entries:
            self.raise_login_required()

        return self.playlist_result(entries, video_id, **event_data)
