import re
from typing import List, Optional, Tuple, Union
from urllib.parse import urljoin, urlparse

from lxml import etree

from app.runtime.settings import RuntimeSettingsCompat

settings = RuntimeSettingsCompat()
from app.domain.context import Context
from app.application.site.query import get_configured_site_query_service
from app.application.site.sites import SitesHelper  # pylint: disable=import-error,no-name-in-module
from app.runtime.log import logger
from app.modules import _ModuleBase
from app.schemas.types import ModuleType, OtherModulesType
from app.adapters.network.http import RequestUtils


class SubtitleModule(_ModuleBase):
    """
    字幕下载模块
    """

    # 站点详情页字幕下载元素识别XPATH
    _SITE_SUBTITLE_XPATH = [
        '//td[@class="rowhead"][text()="字幕"]/following-sibling::td//a[not(@class)]',
        '//td[@class="rowhead"][text()="字幕"]/following-sibling::td//a',
        '//div[contains(@class, "font-bold")][text()="字幕"]/following-sibling::div[1]//a[not(@class)]', # 憨憨
    ]
    _SUBTITLE_URL_ATTRS = (
        "href",
        "data-url",
        "data-href",
        "data-link",
        "data-download",
        "data-download-url",
    )
    _SCRIPT_URL_RE = re.compile(
        r"""["'](?P<url>(?:https?:)?//[^"']+|/[^"']+|[^"']*(?:download|subtitle|subs?)[^"']*)["']""",
        re.IGNORECASE,
    )

    def init_module(self) -> None:
        pass

    @staticmethod
    def get_name() -> str:
        return "站点字幕"

    @staticmethod
    def get_type() -> ModuleType:
        """
        获取模块类型
        """
        return ModuleType.Other

    @staticmethod
    def get_subtype() -> OtherModulesType:
        """
        获取模块子类型
        """
        return OtherModulesType.Subtitle

    @staticmethod
    def get_priority() -> int:
        """
        获取模块优先级，数字越小优先级越高，只有同一接口下优先级才生效
        """
        return 0

    def init_setting(self) -> Tuple[str, Union[str, bool]]:
        pass

    def stop(self) -> None:
        pass

    def test(self):
        pass

    @classmethod
    def __normalize_subtitle_link(cls, page_url: str, sublink: str) -> Optional[str]:
        """
        转换并过滤真实字幕下载链接
        """
        if not sublink:
            return None
        sublink = sublink.strip()
        if not sublink or sublink.startswith("#"):
            return None
        parsed = urlparse(sublink)
        if parsed.scheme and parsed.scheme not in ("http", "https"):
            return None
        if sublink.startswith("//"):
            page_scheme = urlparse(page_url).scheme or "https"
            sublink = f"{page_scheme}:{sublink}"
        else:
            sublink = urljoin(page_url, sublink)
        parsed = urlparse(sublink)
        if parsed.scheme not in ("http", "https") or not parsed.netloc:
            return None
        return sublink

    @classmethod
    def _parse_subtitle_links(cls, html, page_url: str) -> List[str]:
        """
        从站点详情页中解析字幕下载链接
        """
        sublink_list = []
        found_links = set()
        for xpath in cls._SITE_SUBTITLE_XPATH:
            sublink_count = len(sublink_list)
            sublink_nodes = html.xpath(xpath)
            if sublink_nodes:
                for sublink_node in sublink_nodes:
                    sublinks = [sublink_node.get(attr) for attr in cls._SUBTITLE_URL_ATTRS]
                    sublinks.extend(
                        match.group("url")
                        for match in cls._SCRIPT_URL_RE.finditer(sublink_node.get("onclick") or "")
                    )
                    for sublink in sublinks:
                        sublink = cls.__normalize_subtitle_link(page_url, sublink)
                        if not sublink or sublink in found_links:
                            continue
                        found_links.add(sublink)
                        sublink_list.append(sublink)
                # 已成功匹配字幕区域，后续xpath可以忽略
                if len(sublink_list) > sublink_count:
                    break
        return sublink_list

    def site_subtitle_links(self, context: Context) -> Optional[List[str]]:
        """
        解析普通站点详情页获取字幕下载链接
        :param context:  上下文，包括识别信息、媒体信息、种子信息
        :return: 字幕下载链接列表，无法访问页面时返回None
        """
        torrent = context.torrent_info
        if not torrent.page_url:
            return None
        # 采用API访问的站点由对应爬虫模块处理，详情页HTML不含字幕元素
        if torrent.site is not None:
            site = get_configured_site_query_service().get_sync(torrent.site)
            if site and (indexer := SitesHelper().get_indexer(site.domain)):
                if indexer.get("parser") == "mTorrent":
                    return None
        request = RequestUtils(
            cookies=torrent.site_cookie,
            ua=torrent.site_ua,
            proxies=settings.PROXY if torrent.site_proxy else None,
        )
        res = request.get_res(torrent.page_url)
        if res and res.status_code == 200:
            if not res.text:
                logger.warn(f"读取页面代码失败：{torrent.page_url}")
                return []
            html = etree.HTML(res.text)
            try:
                return self._parse_subtitle_links(html, torrent.page_url)
            finally:
                if html is not None:
                    del html
        elif res is not None:
            logger.warn(f"连接 {torrent.page_url} 失败，状态码：{res.status_code}")
        else:
            logger.warn(f"无法打开链接：{torrent.page_url}")
        return None
