#!/usr/bin/env python3
"""
data_security.py — 数据安全与隐私保护模块

职责:
  1. 数据加密 (AES-256-GCM) — 存储加密
  2. 传输安全 (TLS 1.3) — 网络传输
  3. 访问控制 (API Key + JWT) — 身份验证
  4. 数据脱敏 (SHA256 哈希 + 字段掩码) — 隐私保护
  5. 审计日志 — 操作追踪
  6. 权限管理 — 细粒度访问控制

安全等级:
  - L1 (基础): API Key + TLS
  - L2 (标准): API Key + TLS + 数据脱敏
  - L3 (高): API Key + TLS + 加密存储 + 审计日志
"""
from __future__ import annotations

import hashlib
import hmac
import json
import os
import secrets
import time
from base64 import b64decode, b64encode
from collections import defaultdict
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional

# ── 配置 ─────────────────────────────────────────────────────────

APP_DIR = Path(__file__).resolve().parent.parent.parent
SECURITY_LOG = APP_DIR / "logs" / "security_audit.ndjson"
PERMISSIONS_FILE = APP_DIR / "config" / "permissions.json"

SECURITY_LEVEL = os.environ.get("SECURITY_LEVEL", "L2")

# ── 数据类 ───────────────────────────────────────────────────────

@dataclass
class AuditEntry:
    """审计日志条目"""
    ts: str
    user: str
    action: str
    resource: str
    result: str  # SUCCESS / DENIED / ERROR
    ip: str = ""
    details: dict = field(default_factory=dict)


@dataclass
class APIKeyInfo:
    """API Key 信息"""
    key: str
    tenant_id: str
    created_at: str
    permissions: list[str] = field(default_factory=list)
    rate_limit: int = 60  # 每分钟请求数


# ── 加密模块 (简化版, 生产环境使用专业库) ───────────────────────

class SimpleCrypto:
    """简单加密工具 (用于演示, 生产环境使用 cryptography 库)"""

    @staticmethod
    def sha256_hash(data: str, salt: str = "") -> str:
        """SHA256 哈希"""
        return hashlib.sha256(f"{salt}{data}".encode()).hexdigest()

    @staticmethod
    def hmac_sign(data: str, key: str) -> str:
        """HMAC-SHA256 签名"""
        return hmac.new(key.encode(), data.encode(), hashlib.sha256).hexdigest()

    @staticmethod
    def mask_value(value: str, visible_chars: int = 4) -> str:
        """掩码值 (显示前N个字符)"""
        if not value:
            return ""
        if len(value) <= visible_chars:
            return "*" * len(value)
        return value[:visible_chars] + "*" * (len(value) - visible_chars)

    @staticmethod
    def mask_email(email: str) -> str:
        """脱敏邮箱"""
        if "@" not in email:
            return mask_value(email)
        user, domain = email.split("@", 1)
        if len(user) <= 2:
            return f"*@{domain}"
        return f"{user[0]}**@{domain}"

    @staticmethod
    def generate_api_key(prefix: str = "lkq") -> str:
        """生成 API Key"""
        random_part = secrets.token_hex(24)
        return f"{prefix}_{random_part}"


# ── 访问控制 ─────────────────────────────────────────────────────

class AccessControl:
    """访问控制器"""

    PERMISSIONS = {
        "read": ["GET"],
        "write": ["POST", "PUT", "PATCH"],
        "delete": ["DELETE"],
        "admin": ["GET", "POST", "PUT", "PATCH", "DELETE"],
        "data_ingest": ["POST"],
        "data_query": ["GET"],
        "data_export": ["GET"],
        "report_generate": ["GET", "POST"],
    }

    def __init__(self):
        self._api_keys: dict[str, APIKeyInfo] = self._load_keys()

    def _load_keys(self) -> dict[str, APIKeyInfo]:
        """加载 API Key 配置"""
        if PERMISSIONS_FILE.exists():
            try:
                data = json.loads(PERMISSIONS_FILE.read_text(encoding="utf-8"))
                keys = {}
                for k, v in data.get("api_keys", {}).items():
                    keys[k] = APIKeyInfo(**v)
                return keys
            except Exception:
                pass
        return {}

    def verify_api_key(self, api_key: str) -> Optional[APIKeyInfo]:
        """验证 API Key"""
        if api_key in self._api_keys:
            self._log_audit("verify_api_key", "api_key", "SUCCESS", details={"key_prefix": api_key[:8]})
            return self._api_keys[api_key]
        return None

    def check_permission(self, api_key: str, method: str, path: str) -> bool:
        """检查权限"""
        info = self.verify_api_key(api_key)
        if not info:
            self._log_audit("permission_check", path, "DENIED", details={"reason": "invalid_key"})
            return False

        # 检查权限列表
        for perm in info.permissions:
            if perm in self.PERMISSIONS:
                if method in self.PERMISSIONS[perm]:
                    self._log_audit("permission_check", path, "SUCCESS", details={"perm": perm})
                    return True

        self._log_audit("permission_check", path, "DENIED", details={"reason": "no_permission"})
        return False

    def _log_audit(self, action: str, resource: str, result: str, details: dict = None) -> None:
        """记录审计日志"""
        entry = AuditEntry(
            ts=datetime.now(timezone.utc).isoformat(),
            user="api_key",
            action=action,
            resource=resource,
            result=result,
            details=details or {},
        )
        self._append_audit(entry)

    def _append_audit(self, entry: AuditEntry) -> None:
        """追加审计日志"""
        if SECURITY_LOG.parent.exists():
            with open(SECURITY_LOG, "a", encoding="utf-8") as f:
                f.write(json.dumps(asdict(entry), ensure_ascii=False) + "\n")


# ── 速率限制 ─────────────────────────────────────────────────────

class RateLimiter:
    """速率限制器 (滑动窗口)"""

    def __init__(self, window_sec: int = 60):
        self.window_sec = window_sec
        self._requests: dict[str, list[float]] = defaultdict(list)

    def check_rate_limit(self, key: str, limit: int) -> tuple[bool, int]:
        """
        检查速率限制
        返回: (是否允许, 剩余请求数)
        """
        now = time.time()
        window_start = now - self.window_sec

        # 清理过期请求
        self._requests[key] = [t for t in self._requests[key] if t > window_start]

        if len(self._requests[key]) >= limit:
            return False, 0

        self._requests[key].append(now)
        return True, limit - len(self._requests[key])

    def reset(self, key: str) -> None:
        """重置速率限制"""
        if key in self._requests:
            del self._requests[key]


# ── 数据脱敏器 ──────────────────────────────────────────────────

class DataSanitizer:
    """数据脱敏处理器"""

    # 敏感字段列表
    SENSITIVE_FIELDS = [
        "tenant_id", "machine_id", "account_id", "email",
        "phone", "name", "address", "credit_card",
    ]

    # 哈希字段 (需要保持可链接性)
    HASH_FIELDS = ["tenant_id", "account_id"]

    def anonymize_event(self, event: dict, salt: str = "") -> dict:
        """
        脱敏单条事件
        - 租户ID: SHA256哈希 (保留前10字符)
        - 账户ID: 移除
        - 邮箱: 部分掩码
        - 时间戳: 精度降至1分钟
        """
        if not salt:
            salt = os.environ.get("ANON_SALT", "LobsterQuant2026")

        anonymized = {}

        for k, v in event.items():
            # 哈希敏感标识符
            if k in self.HASH_FIELDS and v:
                anonymized[k + "_hash"] = SimpleCrypto.sha256_hash(str(v), salt)[:10]
                if k != "tenant_id":  # tenant_id 用 hash 替代
                    continue

            # 移除完全敏感字段
            if k in self.SENSITIVE_FIELDS and k not in self.HASH_FIELDS:
                continue

            # 邮箱脱敏
            if k == "email" and v:
                anonymized[k] = SimpleCrypto.mask_email(str(v))

            # 时间戳精度降至分钟
            elif k == "ts" and v:
                try:
                    dt = datetime.fromisoformat(str(v).replace("Z", "+00:00"))
                    anonymized[k] = dt.strftime("%Y-%m-%dT%H:%M") + ":00Z"
                except ValueError:
                    anonymized[k] = v

            else:
                anonymized[k] = v

        return anonymized

    def anonymize_batch(self, events: list[dict]) -> list[dict]:
        """批量脱敏"""
        return [self.anonymize_event(e) for e in events]


# ── 安全中间件 ──────────────────────────────────────────────────

class SecurityMiddleware:
    """安全中间件 (用于 FastAPI 等框架)"""

    def __init__(self):
        self.access_control = AccessControl()
        self.rate_limiter = RateLimiter()
        self.sanitizer = DataSanitizer()

    def verify_request(
        self,
        api_key: str,
        method: str,
        path: str,
        rate_limit: int = 60,
    ) -> tuple[bool, str]:
        """
        验证请求安全性
        返回: (是否通过, 错误消息)
        """
        # 检查 API Key
        key_info = self.access_control.verify_api_key(api_key)
        if not key_info:
            return False, "Invalid API Key"

        # 检查权限
        if not self.access_control.check_permission(api_key, method, path):
            return False, "Permission denied"

        # 检查速率限制
        allowed, remaining = self.rate_limiter.check_rate_limit(api_key, rate_limit)
        if not allowed:
            return False, f"Rate limit exceeded (limit: {rate_limit}/min)"

        return True, ""

    def sanitize_data(self, data: dict) -> dict:
        """脱敏数据 (输出前调用)"""
        return self.sanitizer.anonymize_event(data)


# ── 安全报告生成 ─────────────────────────────────────────────────

def generate_security_report(hours: int = 24) -> dict[str, Any]:
    """生成安全报告"""
    audit_entries = []
    cutoff = datetime.now(timezone.utc).timestamp() - hours * 3600

    if SECURITY_LOG.exists():
        with open(SECURITY_LOG, "r", encoding="utf-8") as f:
            for line in f:
                try:
                    entry = json.loads(line.strip())
                    ts = entry.get("ts", "")
                    if ts:
                        try:
                            dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
                            if dt.timestamp() >= cutoff:
                                audit_entries.append(entry)
                        except ValueError:
                            continue
                except json.JSONDecodeError:
                    continue

    denied_count = sum(1 for e in audit_entries if e.get("result") == "DENIED")
    success_count = sum(1 for e in audit_entries if e.get("result") == "SUCCESS")

    return {
        "period_hours": hours,
        "total_events": len(audit_entries),
        "successful_access": success_count,
        "denied_access": denied_count,
        "deny_rate_pct": round(denied_count / len(audit_entries) * 100, 2) if audit_entries else 0,
        "recent_denials": [e for e in audit_entries if e.get("result") == "DENIED"][-5:],
        "generated_at": datetime.now(timezone.utc).isoformat(),
    }


# ── 便捷函数 ─────────────────────────────────────────────────────

_access_control: AccessControl | None = None
_security_middleware: SecurityMiddleware | None = None


def get_access_control() -> AccessControl:
    global _access_control
    if _access_control is None:
        _access_control = AccessControl()
    return _access_control


def get_security_middleware() -> SecurityMiddleware:
    global _security_middleware
    if _security_middleware is None:
        _security_middleware = SecurityMiddleware()
    return _security_middleware


def verify_api_key(api_key: str) -> bool:
    """验证 API Key (简单封装)"""
    return get_access_control().verify_api_key(api_key) is not None


def sanitize_output(data: dict) -> dict:
    """脱敏输出数据"""
    return get_security_middleware().sanitize_data(data)


# ── 主入口 ───────────────────────────────────────────────────────

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Data security utilities")
    parser.add_argument("--gen-key", action="store_true", help="Generate new API key")
    parser.add_argument("--security-report", action="store_true", help="Generate security report")
    parser.add_argument("--sanitize", type=str, help="Sanitize sample data")
    args = parser.parse_args()

    if args.gen_key:
        key = SimpleCrypto.generate_api_key()
        print(f"Generated API Key: {key}")

    elif args.security_report:
        report = generate_security_report()
        print(json.dumps(report, ensure_ascii=False, indent=2))

    elif args.sanitize:
        try:
            data = json.loads(args.sanitize)
            sanitized = DataSanitizer().anonymize_event(data)
            print(json.dumps(sanitized, ensure_ascii=False, indent=2))
        except json.JSONDecodeError:
            print("Invalid JSON input")