import re
from typing import Any, Dict
from typing import List, Tuple
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse

import sentry_sdk
from app.plugins import _PluginBase
from version import APP_VERSION


class SentrySanitizer:
    # 常见敏感字段名（可自行扩展）
    SENSITIVE_KEYS = {
        "password", "passwd", "pwd",
        "secret", "token", "access_token", "refresh_token",
        "authorization", "api_key", "apikey",
        "cookie", "set-cookie", "passkey",
        "key", "credential", "auth", "login", "user", "username",
        "email", "phone", "address", "ip", "host", "domain"
    }

    # 匹配包含敏感关键词的正则
    SENSITIVE_PATTERN = re.compile(
        "|".join(re.escape(key) for key in SENSITIVE_KEYS), re.IGNORECASE
    )

    # 网络连接错误类异常（不上报）
    NETWORK_ERRORS = {
        "ConnectionError", "ConnectionRefusedError", "ConnectionAbortedError",
        "ConnectionResetError", "TimeoutError", "socket.timeout", "socket.error",
        "ssl.SSLError", "ssl.SSLCertVerificationError", "ssl.SSLWantReadError",
        "ssl.SSLWantWriteError", "ssl.SSLZeroReturnError", "ssl.SSLSyscallError",
        "urllib.error.URLError", "urllib.error.HTTPError", "requests.exceptions.ConnectionError",
        "requests.exceptions.Timeout", "requests.exceptions.ConnectTimeout",
        "requests.exceptions.ReadTimeout", "requests.exceptions.SSLError",
        "aiohttp.ClientConnectionError", "aiohttp.ClientTimeout", "aiohttp.ServerTimeoutError",
        "aiohttp.ServerDisconnectedError", "aiohttp.ClientOSError"
    }

    # 网络连接错误关键词
    NETWORK_ERROR_KEYWORDS = [
        "connection", "timeout", "network", "dns", "ssl", "certificate",
        "refused", "reset", "aborted", "unreachable", "no route to host",
        "name or service not known", "temporary failure", "network is unreachable",
        "SOCKSHTTPSConnectionPool", "ERR_HTTP_RESPONSE_CODE_FAILURE", "HTTPSConnectionPool",
        "网络连接", "无法连接", "请求失败", "下载失败", "请求返回空值", "图片失败", "未获取到返回数据",
        "请求返回空值", "返回空响应", "连接出错", "请求错误", "未获取到"
    ]

    @classmethod
    def scrub_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]:
        """
        递归清洗字典中的敏感信息
        """
        if not isinstance(data, dict):
            return data

        sanitized = {}
        for key, value in data.items():
            if isinstance(value, dict):
                sanitized[key] = cls.scrub_dict(value)
            elif isinstance(value, list):
                sanitized[key] = [cls.scrub_dict(v) if isinstance(v, dict) else v for v in value]
            else:
                if cls.SENSITIVE_PATTERN.search(str(key)):
                    sanitized[key] = "[Filtered]"
                else:
                    sanitized[key] = value
        return sanitized

    @classmethod
    def scrub_url(cls, url: str) -> str:
        """
        清理 URL 中的敏感 query 参数
        """
        try:
            parsed = urlparse(url)
            query = parse_qs(parsed.query, keep_blank_values=True)
            for key in query:
                if cls.SENSITIVE_PATTERN.search(key):
                    query[key] = ["[Filtered]"]
            new_query = urlencode(query, doseq=True)
            return urlunparse(parsed._replace(query=new_query))
        except Exception as err:
            print(str(err))
            return url

    @classmethod
    def is_network_error(cls, event) -> bool:
        """
        判断是否为网络连接错误类异常
        """
        # 检查异常类型
        if "exception" in event:
            for exc in event["exception"].get("values", []):
                if "type" in exc:
                    exc_type = exc["type"]
                    if exc_type in cls.NETWORK_ERRORS:
                        return True
                
                # 检查异常消息是否包含网络错误关键词
                if "value" in exc:
                    exc_value = exc["value"].lower()
                    for keyword in cls.NETWORK_ERROR_KEYWORDS:
                        if keyword in exc_value:
                            return True
        
        # 检查日志消息
        if "message" in event:
            message = event["message"].lower()
            for keyword in cls.NETWORK_ERROR_KEYWORDS:
                if keyword in message:
                    return True
        
        return False

    TRACEBACK_PATTERN = re.compile(
        r"Traceback \(most recent call last\):|"
        r"File \"[^\"]+\", line \d+|"
        r"^\s+raise \w+|"
        r"^\w+Error:|^\w+Exception:",
        re.MULTILINE
    )

    @classmethod
    def has_stacktrace(cls, event) -> bool:
        """
        判断事件是否包含明确的异常堆栈信息（结构化异常或日志文本中的堆栈）
        """
        if "exception" in event:
            for exc in event["exception"].get("values", []):
                stacktrace = exc.get("stacktrace")
                if stacktrace and stacktrace.get("frames"):
                    return True

        if "message" in event and cls.TRACEBACK_PATTERN.search(event["message"]):
            return True

        if "logentry" in event:
            msg = event["logentry"].get("message", "") or event["logentry"].get("formatted", "")
            if cls.TRACEBACK_PATTERN.search(msg):
                return True

        return False

    @classmethod
    def before_send(cls, event, hint):
        """
        在发送到 Sentry 之前脱敏和过滤
        """
        # 只上报包含明确异常堆栈的事件，普通 error 日志不上报
        if not cls.has_stacktrace(event):
            return None

        # 如果是网络连接错误，直接返回 None 不上报
        if cls.is_network_error(event):
            return None
        
        # 处理 request 数据
        request = event.get("request", {})
        if "url" in request:
            request["url"] = cls.scrub_url(request["url"])
        if "headers" in request:
            request["headers"] = cls.scrub_dict(request["headers"])
        if "data" in request:
            request["data"] = cls.scrub_dict(request["data"])
        if "cookies" in request:
            request["cookies"] = cls.scrub_dict(request["cookies"])

        # 处理 user 数据
        if "user" in event:
            event["user"] = cls.scrub_dict(event["user"])

        # 处理 extra 数据
        if "extra" in event:
            event["extra"] = cls.scrub_dict(event["extra"])

        # 处理异常信息（避免敏感数据出现在 message 中）
        if "exception" in event:
            for exc in event["exception"].get("values", []):
                if "value" in exc and cls.SENSITIVE_PATTERN.search(exc["value"]):
                    exc["value"] = "[Filtered Exception Message]"
                
                # 清理异常堆栈中的敏感信息
                if "stacktrace" in exc and "frames" in exc["stacktrace"]:
                    for frame in exc["stacktrace"]["frames"]:
                        if "vars" in frame:
                            frame["vars"] = cls.scrub_dict(frame["vars"])
                        if "context_line" in frame and cls.SENSITIVE_PATTERN.search(frame["context_line"]):
                            frame["context_line"] = "[Filtered]"

        # 清理消息中的敏感信息
        if "message" in event and cls.SENSITIVE_PATTERN.search(event["message"]):
            event["message"] = "[Filtered Message]"

        return event


class BugReporter(_PluginBase):
    # 插件名称
    plugin_name = "Bug反馈"
    # 插件描述
    plugin_desc = "自动上报异常，协助开发者发现和解决问题。"
    # 插件图标
    plugin_icon = "Alist_encrypt_A.png"
    # 插件版本
    plugin_version = "1.5.1"
    # 插件作者
    plugin_author = "jxxghp"
    # 作者主页
    author_url = "https://github.com/jxxghp"
    # 插件配置项ID前缀
    plugin_config_prefix = "bugreporter_"
    # 加载顺序
    plugin_order = 99
    # 可使用的用户级别
    auth_level = 1

    _enable: bool = False

    def init_plugin(self, config: dict = None):
        self._enable = config.get("enable")
        if self._enable:
            sentry_sdk.init("https://3999f6a035db46a588b03e5a92b9f592@glitchtip.movie-pilot.org/2",
                            before_send=SentrySanitizer.before_send,
                            release=APP_VERSION,
                            send_default_pii=False)

    @staticmethod
    def get_command() -> List[Dict[str, Any]]:
        pass

    def get_api(self) -> List[Dict[str, Any]]:
        pass

    def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
        return [
            {
                'component': 'VForm',
                'content': [
                    {
                        'component': 'VRow',
                        'content': [
                            {
                                'component': 'VCol',
                                'props': {
                                    'cols': 12,
                                    'md': 6
                                },
                                'content': [
                                    {
                                        'component': 'VSwitch',
                                        'props': {
                                            'model': 'enable',
                                            'label': '启用插件',
                                        }
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        'component': 'VRow',
                        'content': [
                            {
                                'component': 'VCol',
                                'props': {
                                    'cols': 12,
                                },
                                'content': [
                                    {
                                        'component': 'VAlert',
                                        'props': {
                                            'type': 'warning',
                                            'variant': 'tonal',
                                            'text': '注意：开启插件即代表你同意将部分异常信息自动发送给开发者，以帮助改进软件；如果你不希望自动发送任何数据，请关闭或卸载此插件；仅上报包含异常堆栈的系统错误，普通日志和网络连接错误不会上报；不会包含任何个人隐私信息或敏感数据；异常信息采集为使用开源项目解决方案：GlitchTip。',
                                        }
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ], {
            "enable": self._enable,
        }

    def get_page(self) -> List[dict]:
        pass

    def get_state(self) -> bool:
        return self._enable

    def stop_service(self):
        pass
