"""
HTTP Header 安全检查
检查 CSP、HSTS、X-Frame-Options 等安全头
"""
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any
from datetime import datetime
from playwright.async_api import Page, Response
from loguru import logger


@dataclass
class HeaderCheckConfig:
    """Header 检查配置"""
    check_csp: bool = True
    check_hsts: bool = True
    check_frame_options: bool = True
    check_content_type_options: bool = True
    check_xss_protection: bool = True
    check_referrer_policy: bool = True
    check_permissions_policy: bool = True
    strict_mode: bool = False


@dataclass
class HeaderIssue:
    """Header 问题"""
    severity: str  # high, medium, low, info
    header: str
    current_value: Optional[str]
    issue: str
    recommendation: str
    reference: Optional[str] = None


@dataclass
class HeaderCheckResult:
    """Header 检查结果"""
    url: str
    headers: Dict[str, str] = field(default_factory=dict)
    issues: List[HeaderIssue] = field(default_factory=list)
    score: int = 100  # 安全评分 0-100
    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())

    @property
    def has_issues(self) -> bool:
        return len(self.issues) > 0

    @property
    def high_severity_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == "high")

    @property
    def medium_severity_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == "medium")


class HeaderChecker:
    """
    HTTP Header 安全检查器
    检查常见安全头配置
    """

    # 推荐的安全 Headers
    RECOMMENDED_HEADERS = {
        "content-security-policy": {
            "severity": "high",
            "description": "内容安全策略，防止 XSS 和数据注入",
            "recommendation": "设置严格的 CSP，限制脚本来源",
        },
        "strict-transport-security": {
            "severity": "high",
            "description": "HSTS，强制使用 HTTPS",
            "recommendation": "设置 max-age 至少 31536000 秒",
        },
        "x-frame-options": {
            "severity": "medium",
            "description": "防止点击劫持",
            "recommendation": "设置为 DENY 或 SAMEORIGIN",
        },
        "x-content-type-options": {
            "severity": "medium",
            "description": "防止 MIME 类型嗅探",
            "recommendation": "设置为 nosniff",
        },
        "x-xss-protection": {
            "severity": "low",
            "description": "XSS 过滤器（已弃用但仍建议设置）",
            "recommendation": "设置为 0 以禁用（现代浏览器已移除此功能）",
        },
        "referrer-policy": {
            "severity": "medium",
            "description": "控制 Referrer 信息",
            "recommendation": "设置为 strict-origin-when-cross-origin",
        },
        "permissions-policy": {
            "severity": "medium",
            "description": "控制浏览器功能权限",
            "recommendation": "限制摄像头、麦克风等敏感功能",
        },
    }

    def __init__(
        self,
        page: Page,
        config: Optional[HeaderCheckConfig] = None,
    ):
        self.page = page
        self.config = config or HeaderCheckConfig()

    # ==================== 检查方法 ====================

    async def check(self, url: Optional[str] = None) -> HeaderCheckResult:
        """
        执行 Header 安全检查

        Args:
            url: 要检查的 URL

        Returns:
            HeaderCheckResult: 检查结果
        """
        target_url = url or self.page.url
        result = HeaderCheckResult(url=target_url)

        # 获取响应头
        headers = await self._fetch_headers(target_url)
        result.headers = headers

        # 检查各个安全头
        if self.config.check_csp:
            self._check_csp(headers, result)

        if self.config.check_hsts:
            self._check_hsts(headers, result)

        if self.config.check_frame_options:
            self._check_frame_options(headers, result)

        if self.config.check_content_type_options:
            self._check_content_type_options(headers, result)

        if self.config.check_xss_protection:
            self._check_xss_protection(headers, result)

        if self.config.check_referrer_policy:
            self._check_referrer_policy(headers, result)

        if self.config.check_permissions_policy:
            self._check_permissions_policy(headers, result)

        # 计算安全评分
        result.score = self._calculate_score(result)

        logger.info(
            f"Header 检查完成: {target_url}, "
            f"评分: {result.score}, 问题: {len(result.issues)}"
        )

        return result

    async def check_multiple_urls(self, urls: List[str]) -> Dict[str, HeaderCheckResult]:
        """
        检查多个 URL

        Args:
            urls: URL 列表

        Returns:
            Dict[str, HeaderCheckResult]: 结果映射
        """
        results = {}

        for url in urls:
            try:
                result = await self.check(url)
                results[url] = result
            except Exception as e:
                logger.error(f"检查失败: {url}, error={e}")

        return results

    # ==================== 具体检查 ====================

    def _check_csp(self, headers: Dict[str, str], result: HeaderCheckResult):
        """检查 CSP"""
        csp = self._get_header(headers, "content-security-policy")

        if not csp:
            result.issues.append(HeaderIssue(
                severity="high",
                header="Content-Security-Policy",
                current_value=None,
                issue="缺少 Content-Security-Policy 头",
                recommendation="添加 CSP 头以防止 XSS 和注入攻击",
                reference="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP",
            ))
            return

        # 检查 CSP 配置
        csp_issues = self._analyze_csp(csp)
        for issue in csp_issues:
            result.issues.append(HeaderIssue(
                severity=issue.get("severity", "medium"),
                header="Content-Security-Policy",
                current_value=csp,
                issue=issue.get("issue", ""),
                recommendation=issue.get("recommendation", ""),
            ))

    def _check_hsts(self, headers: Dict[str, str], result: HeaderCheckResult):
        """检查 HSTS"""
        hsts = self._get_header(headers, "strict-transport-security")

        if not hsts:
            result.issues.append(HeaderIssue(
                severity="high",
                header="Strict-Transport-Security",
                current_value=None,
                issue="缺少 HSTS 头",
                recommendation="添加 HSTS 头以强制使用 HTTPS",
            ))
            return

        # 检查 max-age
        if "max-age" in hsts:
            max_age = self._extract_max_age(hsts)
            if max_age and max_age < 31536000:  # 少于一年
                result.issues.append(HeaderIssue(
                    severity="medium",
                    header="Strict-Transport-Security",
                    current_value=hsts,
                    issue="max-age 值过小",
                    recommendation="将 max-age 设置为至少 31536000 秒（一年）",
                ))

        # 检查 includeSubDomains
        if "includeSubDomains" not in hsts and self.config.strict_mode:
            result.issues.append(HeaderIssue(
                severity="low",
                header="Strict-Transport-Security",
                current_value=hsts,
                issue="未包含 includeSubDomains 指令",
                recommendation="添加 includeSubDomains 以保护子域名",
            ))

    def _check_frame_options(self, headers: Dict[str, str], result: HeaderCheckResult):
        """检查 X-Frame-Options"""
        xfo = self._get_header(headers, "x-frame-options")

        if not xfo:
            result.issues.append(HeaderIssue(
                severity="medium",
                header="X-Frame-Options",
                current_value=None,
                issue="缺少 X-Frame-Options 头",
                recommendation="添加 X-Frame-Options 防止点击劫持",
            ))
            return

        if xfo.upper() not in ["DENY", "SAMEORIGIN"]:
            result.issues.append(HeaderIssue(
                severity="medium",
                header="X-Frame-Options",
                current_value=xfo,
                issue="X-Frame-Options 值不安全",
                recommendation="设置为 DENY 或 SAMEORIGIN",
            ))

    def _check_content_type_options(self, headers: Dict[str, str], result: HeaderCheckResult):
        """检查 X-Content-Type-Options"""
        xcto = self._get_header(headers, "x-content-type-options")

        if not xcto:
            result.issues.append(HeaderIssue(
                severity="medium",
                header="X-Content-Type-Options",
                current_value=None,
                issue="缺少 X-Content-Type-Options 头",
                recommendation="添加 X-Content-Type-Options: nosniff",
            ))
            return

        if xcto.lower() != "nosniff":
            result.issues.append(HeaderIssue(
                severity="low",
                header="X-Content-Type-Options",
                current_value=xcto,
                issue="X-Content-Type-Options 值不正确",
                recommendation="设置为 nosniff",
            ))

    def _check_xss_protection(self, headers: Dict[str, str], result: HeaderCheckResult):
        """检查 X-XSS-Protection"""
        xss = self._get_header(headers, "x-xss-protection")

        # X-XSS-Protection 已弃用，但仍检查
        if xss and "1" in xss and "mode=block" not in xss:
            result.issues.append(HeaderIssue(
                severity="info",
                header="X-XSS-Protection",
                current_value=xss,
                issue="X-XSS-Protection 应使用 block 模式",
                recommendation="设置为 0 以禁用（现代浏览器已弃用此功能）",
            ))

    def _check_referrer_policy(self, headers: Dict[str, str], result: HeaderCheckResult):
        """检查 Referrer-Policy"""
        rp = self._get_header(headers, "referrer-policy")

        if not rp:
            result.issues.append(HeaderIssue(
                severity="medium",
                header="Referrer-Policy",
                current_value=None,
                issue="缺少 Referrer-Policy 头",
                recommendation="添加 Referrer-Policy 控制信息泄露",
            ))
            return

        safe_values = [
            "no-referrer",
            "no-referrer-when-downgrade",
            "strict-origin",
            "strict-origin-when-cross-origin",
            "same-origin",
        ]

        if rp.lower() not in safe_values:
            result.issues.append(HeaderIssue(
                severity="low",
                header="Referrer-Policy",
                current_value=rp,
                issue="Referrer-Policy 值可能不够安全",
                recommendation="使用 strict-origin-when-cross-origin 或更严格的策略",
            ))

    def _check_permissions_policy(self, headers: Dict[str, str], result: HeaderCheckResult):
        """检查 Permissions-Policy"""
        pp = self._get_header(headers, "permissions-policy")

        if not pp and self.config.strict_mode:
            result.issues.append(HeaderIssue(
                severity="low",
                header="Permissions-Policy",
                current_value=None,
                issue="缺少 Permissions-Policy 头",
                recommendation="添加 Permissions-Policy 限制敏感功能",
            ))

    # ==================== 辅助方法 ====================

    async def _fetch_headers(self, url: str) -> Dict[str, str]:
        """获取响应头"""
        try:
            response = await self.page.goto(url, wait_until="domcontentloaded")
            if response:
                return dict(response.headers)
        except Exception as e:
            logger.error(f"获取响应头失败: {url}, error={e}")
        return {}

    def _get_header(self, headers: Dict[str, str], name: str) -> Optional[str]:
        """获取指定 Header（不区分大小写）"""
        for key, value in headers.items():
            if key.lower() == name:
                return value
        return None

    def _analyze_csp(self, csp: str) -> List[Dict[str, str]]:
        """分析 CSP 配置"""
        issues = []

        # 检查 unsafe-inline
        if "unsafe-inline" in csp:
            issues.append({
                "severity": "high",
                "issue": "CSP 包含 unsafe-inline，降低安全性",
                "recommendation": "移除 unsafe-inline，使用 nonce 或 hash",
            })

        # 检查 unsafe-eval
        if "unsafe-eval" in csp:
            issues.append({
                "severity": "high",
                "issue": "CSP 包含 unsafe-eval，允许 eval 执行",
                "recommendation": "移除 unsafe-eval",
            })

        # 检查 data: URI
        if "data:" in csp:
            issues.append({
                "severity": "medium",
                "issue": "CSP 允许 data: URI",
                "recommendation": "移除 data: 源",
            })

        # 检查 * 通配符
        if "*" in csp and "default-src" in csp:
            issues.append({
                "severity": "medium",
                "issue": "CSP 使用通配符源",
                "recommendation": "限制为具体的域名",
            })

        return issues

    def _extract_max_age(self, hsts: str) -> Optional[int]:
        """提取 HSTS max-age 值"""
        import re
        match = re.search(r"max-age=(\d+)", hsts)
        if match:
            return int(match.group(1))
        return None

    def _calculate_score(self, result: HeaderCheckResult) -> int:
        """计算安全评分"""
        score = 100

        for issue in result.issues:
            if issue.severity == "high":
                score -= 20
            elif issue.severity == "medium":
                score -= 10
            elif issue.severity == "low":
                score -= 5

        return max(0, score)

    # ==================== 便捷方法 ====================

    async def quick_check(self) -> bool:
        """
        快速检查，返回是否安全

        Returns:
            bool: 是否通过检查（无高危问题）
        """
        result = await self.check()
        return result.high_severity_count == 0

    async def get_security_score(self, url: Optional[str] = None) -> int:
        """
        获取安全评分

        Args:
            url: 要检查的 URL

        Returns:
            int: 安全评分 0-100
        """
        result = await self.check(url)
        return result.score


def create_header_checker(
    page: Page,
    config: Optional[HeaderCheckConfig] = None,
) -> HeaderChecker:
    """创建 Header 检查器实例"""
    return HeaderChecker(page, config)
