"""用现有 Deno 在无文件、环境和网络权限的进程中执行斗鱼签名脚本。"""
import json
import shutil
import subprocess
import time
import urllib.parse
import uuid

from yt_dlp.extractor.douyutv import DouyuShowIE as UpstreamDouyuShowIE, DouyuTVIE as UpstreamDouyuTVIE
from yt_dlp.utils import (
    ExtractorError,
    UserNotLive,
    int_or_none,
    str_or_none,
    traverse_obj,
    unescapeHTML,
    url_or_none,
    urlencode_postdata,
)


class _DenoSigning:
    def _calc_sign(self, sign_func, video_id, room_id):
        deno = shutil.which('deno')
        if not deno:
            raise ExtractorError('Deno is required for Douyu signing', expected=True)
        script = ('globalThis.window=globalThis;' + self._get_cryptojs_md5(video_id) + ';' + sign_func
                  + ';console.log(ub98484234(' + ','.join(json.dumps(value) for value in
                    (str(room_id), uuid.uuid4().hex, str(round(time.time())))) + '))')
        try:
            result = subprocess.run([deno, 'run', '--no-prompt', '--no-config', '--no-remote', '-'],
                                    input=script, text=True, capture_output=True, timeout=15)
        except (OSError, subprocess.TimeoutExpired) as exc:
            raise ExtractorError('Douyu signing runtime failed', cause=exc) from exc
        lines = result.stdout.strip().splitlines()
        fields = urllib.parse.parse_qs(lines[-1]) if lines and result.returncode == 0 else {}
        if not fields.get('sign'):
            raise ExtractorError('Unable to sign Douyu request with Deno')
        return {key: values[0] for key, values in fields.items()}


class DouyuShowIE(UpstreamDouyuShowIE, plugin_name='DenoSigning'):
    _calc_sign = _DenoSigning._calc_sign


class DouyuTVIE(UpstreamDouyuTVIE, plugin_name='DenoSigning'):
    _calc_sign = _DenoSigning._calc_sign

    def _extract_stream_formats(self, stream_formats):
        normalized = []
        for response in stream_formats:
            data = response.get('data')
            if isinstance(data, dict) and isinstance(data.get('rtmp_url'), str):
                # API 给出的 /live 是目录；补斜杠，避免 urljoin 把它当文件名替换。
                response = {**response, 'data': {
                    **data, 'rtmp_url': data['rtmp_url'].rstrip('/') + '/',
                }}
            normalized.append(response)
        return super()._extract_stream_formats(normalized)

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        room = traverse_obj(
            self._search_nextjs_v13_data(webpage, video_id, fatal=False),
            (..., 'roomInfo', 'room', {dict}), get_all=False)
        if not room:
            # 旧版页面仍交给上游处理，避免从推荐列表或网址猜测房间号。
            return super()._real_extract(url)

        room_id = str_or_none(room.get('room_id'))
        if not room_id or not room_id.isascii() or not room_id.isdecimal() or int(room_id) <= 0:
            raise ExtractorError('Unable to extract room id')
        if traverse_obj(room, ('room_biz_all', 'videoLoop', {int_or_none})) == 1:
            raise UserNotLive('The channel is auto-playing VODs', video_id=video_id)
        if int_or_none(room.get('show_status')) == 2:
            raise UserNotLive(video_id=video_id)

        # 新页面不再内嵌签名函数；沿用上游获取脚本和解析格式的方法。
        sign_func = self._get_sign_func(room_id, video_id)
        form_data = {'rate': 0, **self._calc_sign(sign_func, video_id, room_id)}
        stream_formats = [self._download_json(
            f'https://www.douyu.com/lapi/live/getH5Play/{room_id}', video_id,
            note='Downloading livestream format', data=urlencode_postdata(form_data))]
        for rate_id in traverse_obj(stream_formats[0], ('data', 'multirates', ..., 'rate')):
            if rate_id != traverse_obj(stream_formats[0], ('data', 'rate')):
                form_data['rate'] = rate_id
                stream_formats.append(self._download_json(
                    f'https://www.douyu.com/lapi/live/getH5Play/{room_id}', video_id,
                    note=f'Downloading livestream format {rate_id}', data=urlencode_postdata(form_data)))

        return {
            'id': room_id,
            'display_id': video_id,
            'formats': self._extract_stream_formats(stream_formats),
            'is_live': True,
            **traverse_obj(room, {
                'title': ('room_name', {str}, {unescapeHTML}),
                'description': ('show_details', {str}),
                'uploader': ('nickname', {str}),
                'thumbnail': ('coverSrc', {url_or_none}),
            }),
        }
