"""
安全扫描器
基础安全扫描，检测暴露的敏感信息
"""
import asyncio
import re
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, List, Literal
from playwright.async_api import Page
from loguru import logger


SeverityLevel = Literal["low", "medium", "high", "critical"]


@dataclass
class SecurityIssue:
    """安全问题"""
    id: str
    type: str
    severity: SeverityLevel
    description: str
    location: str
    recommendation: str
    references: List[str] = field(default_factory=list)


@dataclass
class SecurityScanResult:
    """安全扫描结果"""
    is_secure: bool
    risk_level: str
    issues: List[SecurityIssue]
    scanned_at: datetime = field(default_factory=datetime.now)


class SecurityScanner:
    """
    安全扫描器
    检测页面中的安全隐患
    """

    # API Key 模式
    API_KEY_PATTERNS = [
        (re.compile(r'sk-[a-zA-Z0-9]{32,}'), "OpenAI API Key"),
        (re.compile(r'AIza[a-zA-Z0-9_-]{35}'), "Google API Key"),
        (re.compile(r'AKIA[a-zA-Z0-9]{16}'), "AWS Access Key"),
        (re.compile(r'ghp_[a-zA-Z0-9]{36}'), "GitHub Token"),
        (re.compile(r'[a-zA-Z0-9]{40}@clients'), "OAuth Client ID"),
    ]

    # 敏感信息模式
    SENSITIVE_PATTERNS = [
        (re.compile(r'password\s*[:=]\s*["\'][^"\']+["\']', re.IGNORECASE), "密码"),
        (re.compile(r'token\s*[:=]\s*["\'][^"\']+["\']', re.IGNORECASE), "Token"),
        (re.compile(r'secret\s*[:=]\s*["\'][^"\']+["\']', re.IGNORECASE), "Secret"),
        (re.compile(r'api[_-]?key\s*[:=]\s*["\'][^"\']+["\']', re.IGNORECASE), "API Key"),
    ]

    def __init__(self, page: Page):
        self.page = page
        self._issues: List[SecurityIssue] = []

    async def scan(self) -> SecurityScanResult:
        """
        执行完整扫描

        Returns:
            SecurityScanResult: 扫描结果
        """
        self._issues = []

        # 执行各项检查
        await self._check_sensitive_data()
        await self._check_html_comments()
        await self._check_meta_tags()
        await self._check_cookies()
        await self._check_local_storage()

        # 计算风险等级
        risk_level = self._calculate_risk_level()

        is_secure = not any(
            i.severity in ["high", "critical"] for i in self._issues
        )

        logger.debug(f"安全扫描完成: issues={len(self._issues)}, risk={risk_level}")

        return SecurityScanResult(
            is_secure=is_secure,
            risk_level=risk_level,
            issues=self._issues,
        )

    async def _check_sensitive_data(self) -> None:
        """检查敏感数据泄露"""
        content = await self.page.content()

        # 检查 API Key
        for pattern, name in self.API_KEY_PATTERNS:
            matches = pattern.findall(content)
            if matches:
                self._add_issue(
                    id="sensitive-api-key",
                    issue_type="sensitive-data",
                    severity="critical",
                    description=f"发现可能暴露的 {name}",
                    location="页面内容",
                    recommendation="将 API Key 移至服务端，不要在前端暴露",
                )

        # 检查敏感信息
        for pattern, name in self.SENSITIVE_PATTERNS:
            matches = pattern.findall(content)
            if matches:
                self._add_issue(
                    id="sensitive-data-exposure",
                    issue_type="sensitive-data",
                    severity="high",
                    description=f"发现可能暴露的 {name}",
                    location="页面内容",
                    recommendation=f"检查并移除页面中的 {name} 暴露",
                )

    async def _check_html_comments(self) -> None:
        """检查 HTML 注释"""
        comments = await self.page.evaluate("""() => {
            const result = [];
            const walker = document.createTreeWalker(
                document.body,
                NodeFilter.SHOW_COMMENT
            );

            while (walker.nextNode()) {
                const text = walker.currentNode.nodeValue?.trim();
                if (text) result.push(text);
            }

            return result;
        }""")

        sensitive_keywords = ["password", "token", "secret", "api", "key", "debug", "todo", "fixme"]

        for comment in comments:
            comment_lower = comment.lower()
            for keyword in sensitive_keywords:
                if keyword in comment_lower:
                    self._add_issue(
                        id="sensitive-comment",
                        issue_type="info-disclosure",
                        severity="medium",
                        description=f"HTML 注释中可能包含敏感信息: {comment[:50]}...",
                        location="HTML 注释",
                        recommendation="移除生产环境中的敏感注释",
                    )
                    break

    async def _check_meta_tags(self) -> None:
        """检查 Meta 标签"""
        # 检查是否有敏感 meta 标签
        meta_tags = await self.page.evaluate("""() => {
            const metas = document.querySelectorAll('meta');
            return Array.from(metas).map(m => ({
                name: m.getAttribute('name') || m.getAttribute('property'),
                content: m.getAttribute('content')
            })).filter(m => m.name && m.content);
        }""")

        for meta in meta_tags:
            name = meta.get("name", "").lower()
            content = meta.get("content", "")

            # 检查是否暴露了版本信息
            if "version" in name:
                self._add_issue(
                    id="version-disclosure",
                    issue_type="info-disclosure",
                    severity="low",
                    description=f"Meta 标签暴露版本信息: {name}",
                    location="Meta 标签",
                    recommendation="移除版本信息 Meta 标签",
                )

            # 检查 content 中是否有敏感信息
            for pattern, sensitive_name in self.SENSITIVE_PATTERNS:
                if pattern.search(content):
                    self._add_issue(
                        id="sensitive-meta",
                        issue_type="sensitive-data",
                        severity="high",
                        description=f"Meta 标签中包含敏感信息: {sensitive_name}",
                        location=f"Meta: {name}",
                        recommendation="移除 Meta 标签中的敏感信息",
                    )

    async def _check_cookies(self) -> None:
        """检查 Cookies"""
        context = self.page.context
        cookies = await context.cookies()

        for cookie in cookies:
            # 检查是否设置了 HttpOnly
            if not cookie.get("httpOnly", False):
                # 检查是否是敏感 cookie
                name = cookie.get("name", "").lower()
                if any(s in name for s in ["session", "token", "auth", "user"]):
                    self._add_issue(
                        id="cookie-no-httponly",
                        issue_type="cookie-security",
                        severity="medium",
                        description=f"敏感 Cookie 未设置 HttpOnly: {cookie.get('name')}",
                        location="Cookie",
                        recommendation="为敏感 Cookie 设置 HttpOnly 属性",
                    )

            # 检查是否设置了 Secure
            if not cookie.get("secure", False):
                name = cookie.get("name", "").lower()
                if any(s in name for s in ["session", "token", "auth", "user"]):
                    self._add_issue(
                        id="cookie-no-secure",
                        issue_type="cookie-security",
                        severity="medium",
                        description=f"敏感 Cookie 未设置 Secure: {cookie.get('name')}",
                        location="Cookie",
                        recommendation="为敏感 Cookie 设置 Secure 属性",
                    )

    async def _check_local_storage(self) -> None:
        """检查 Local Storage"""
        storage = await self.page.evaluate("""() => {
            const data = {};
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                data[key] = localStorage.getItem(key);
            }
            return data;
        }""")

        sensitive_keywords = ["token", "secret", "password", "api_key", "apikey", "auth"]

        for key, value in storage.items():
            key_lower = key.lower()
            for keyword in sensitive_keywords:
                if keyword in key_lower:
                    self._add_issue(
                        id="sensitive-local-storage",
                        issue_type="sensitive-data",
                        severity="high",
                        description=f"Local Storage 中存储敏感信息: {key}",
                        location="Local Storage",
                        recommendation="不要在 Local Storage 中存储敏感信息",
                    )
                    break

    def _add_issue(
        self,
        id: str,
        issue_type: str,
        severity: SeverityLevel,
        description: str,
        location: str,
        recommendation: str,
        references: Optional[List[str]] = None,
    ) -> None:
        """添加问题"""
        self._issues.append(SecurityIssue(
            id=id,
            type=issue_type,
            severity=severity,
            description=description,
            location=location,
            recommendation=recommendation,
            references=references or [],
        ))

    def _calculate_risk_level(self) -> str:
        """计算风险等级"""
        critical_count = sum(1 for i in self._issues if i.severity == "critical")
        high_count = sum(1 for i in self._issues if i.severity == "high")
        medium_count = sum(1 for i in self._issues if i.severity == "medium")

        if critical_count > 0:
            return "Critical"
        elif high_count > 2:
            return "High"
        elif high_count > 0 or medium_count > 3:
            return "Medium"
        elif medium_count > 0:
            return "Low"
        else:
            return "None"

    def generate_report(self, result: SecurityScanResult) -> str:
        """生成报告"""
        lines = [
            "\n=== 安全扫描报告 ===\n",
            f"风险等级: {result.risk_level}",
            f"安全问题: {len(result.issues)}",
            f"扫描时间: {result.scanned_at.strftime('%Y-%m-%d %H:%M:%S')}\n",
        ]

        if result.issues:
            lines.append("问题详情:")
            for i, issue in enumerate(result.issues, 1):
                lines.extend([
                    f"\n{i}. [{issue.severity.upper()}] {issue.type}",
                    f"   {issue.description}",
                    f"   位置: {issue.location}",
                    f"   建议: {issue.recommendation}",
                ])

        return "\n".join(lines)


def create_security_scanner(page: Page) -> SecurityScanner:
    """创建安全扫描器实例"""
    return SecurityScanner(page)
