#!/usr/bin/env python3
"""
セキュリティ脆弱性スキャナー

S110パターンおよび追加セキュリティ脆弱性を検出・報告する包括的スキャナー
"""

import json
import re
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any

from noveler.domain.value_objects.project_time import project_now
from noveler.infrastructure.config.unified_config_manager import get_configuration_manager
from noveler.presentation.shared.shared_utilities import get_console


class VulnerabilityType(Enum):
    """脆弱性タイプ列挙"""

    HARDCODED_SECRET = "HARDCODED_SECRET"
    PATH_TRAVERSAL = "PATH_TRAVERSAL"
    COMMAND_INJECTION = "COMMAND_INJECTION"
    SQL_INJECTION = "SQL_INJECTION"
    UNSAFE_FILE_OPERATION = "UNSAFE_FILE_OPERATION"
    INSECURE_DESERIALIZATION = "INSECURE_DESERIALIZATION"
    MISSING_INPUT_VALIDATION = "MISSING_INPUT_VALIDATION"
    XML_YAML_INJECTION = "XML_YAML_INJECTION"
    INSECURE_RANDOM = "INSECURE_RANDOM"
    WEAK_CRYPTO = "WEAK_CRYPTO"


@dataclass
class SecurityVulnerability:
    """セキュリティ脆弱性データクラス"""

    vuln_type: VulnerabilityType
    file_path: str
    line_number: int
    code_snippet: str
    severity: str
    description: str
    recommendation: str
    cwe_id: str | None = None
    confidence: str = "HIGH"


@dataclass
class SecurityScanResult:
    """スキャン結果データクラス"""

    vulnerabilities: list[SecurityVulnerability] = field(default_factory=list)
    files_scanned: int = 0
    high_severity_count: int = 0
    medium_severity_count: int = 0
    low_severity_count: int = 0

    def add_vulnerability(self, vuln: SecurityVulnerability) -> None:
        """脆弱性を追加し、統計を更新"""
        self.vulnerabilities.append(vuln)
        if vuln.severity == "HIGH":
            self.high_severity_count += 1
        elif vuln.severity == "MEDIUM":
            self.medium_severity_count += 1
        else:
            self.low_severity_count += 1


class SecurityVulnerabilityScanner:
    """セキュリティ脆弱性スキャナー"""

    def __init__(self, logger_service: Any | None = None, console_service: Any | None = None) -> None:
        self.config = get_configuration_manager()
        self.console = get_console()
        self.scan_patterns = self._initialize_scan_patterns()

        self.logger_service = logger_service
        self.console_service = console_service

    def scan_project(self, project_root: Path | None = None) -> SecurityScanResult:
        """プロジェクト全体のセキュリティスキャン実行"""
        if not project_root:
            project_root = self.config.get_project_root()

        result = SecurityScanResult()
        scripts_dir = project_root / "scripts"

        if not scripts_dir.exists():
            self.console.print("❌ scripts ディレクトリが見つかりません", style="error")
            return result

        self.console.print("🔍 セキュリティ脆弱性スキャンを開始しています...", style="info")

        # .pyファイルを再帰的にスキャン
        for py_file in scripts_dir.rglob("*.py"):
            try:
                self._scan_file(py_file, result)
                result.files_scanned += 1
            except Exception as e:
                self.console.print(f"⚠️  ファイルスキャンエラー: {py_file}: {e}", style="warning")

        self._print_summary(result)
        return result

    def _scan_file(self, file_path: Path, result: SecurityScanResult) -> None:
        """単一ファイルのセキュリティスキャン"""
        try:
            content = file_path.read_text(encoding="utf-8")
            lines = content.split("\n")

            for line_num, line in enumerate(lines, 1):
                for vuln_type, patterns in self.scan_patterns.items():
                    for pattern_data in patterns:
                        if re.search(pattern_data["pattern"], line, re.IGNORECASE):
                            # テストファイルの場合は重要度を下げる
                            severity = "LOW" if "test" in str(file_path).lower() else pattern_data["severity"]

                            vuln = SecurityVulnerability(
                                vuln_type=vuln_type,
                                file_path=str(file_path.relative_to(self.config.get_project_root())),
                                line_number=line_num,
                                code_snippet=line.strip(),
                                severity=severity,
                                description=pattern_data["description"],
                                recommendation=pattern_data["recommendation"],
                                cwe_id=pattern_data.get("cwe_id"),
                                confidence=pattern_data.get("confidence", "HIGH"),
                            )
                            result.add_vulnerability(vuln)

        except UnicodeDecodeError:
            self.console.print(f"⚠️  ファイル読み込みエラー（エンコーディング問題）: {file_path}", style="warning")

    def _initialize_scan_patterns(self) -> dict[VulnerabilityType, list[dict]]:
        """スキャンパターンの初期化"""
        return {
            VulnerabilityType.HARDCODED_SECRET: [
                {
                    "pattern": r'(password|pwd|secret|key|token)\s*[=:]\s*["\'][^"\']{8,}["\']',
                    "severity": "HIGH",
                    "description": "ハードコードされたシークレットが検出されました",
                    "recommendation": "環境変数やセキュアな設定ファイルを使用してください",
                    "cwe_id": "CWE-798",
                },
                {
                    "pattern": r'["\'][A-Za-z0-9+/]{40,}={0,2}["\']',
                    "severity": "MEDIUM",
                    "description": "Base64エンコードされた可能性のあるシークレットが検出されました",
                    "recommendation": "ハードコードされたシークレットではないことを確認してください",
                    "cwe_id": "CWE-798",
                    "confidence": "MEDIUM",
                },
            ],
            VulnerabilityType.PATH_TRAVERSAL: [
                {
                    "pattern": r"\.\./|\.\.\\\|/\.\./|\\\.\.\\\|\.\./",
                    "severity": "HIGH",
                    "description": "パストラバーサル攻撃の可能性があります",
                    "recommendation": "パス正規化とホワイトリスト検証を実装してください",
                    "cwe_id": "CWE-22",
                },
                {
                    "pattern": r"open\s*\(\s*[^,)]*input\(|open\s*\(\s*user_input",
                    "severity": "HIGH",
                    "description": "ユーザー入力を直接ファイル操作に使用しています",
                    "recommendation": "入力検証とパス制限を実装してください",
                    "cwe_id": "CWE-22",
                },
            ],
            VulnerabilityType.COMMAND_INJECTION: [
                {
                    "pattern": r"os\.system\s*\(|subprocess\.call\s*\([^)]*shell\s*=\s*True",
                    "severity": "HIGH",
                    "description": "コマンドインジェクション攻撃の可能性があります",
                    "recommendation": "shellパラメータを避け、引数リストを使用してください",
                    "cwe_id": "CWE-78",
                },
                {
                    "pattern": r'f["\'].*cp\s+.*\{.*\}.*["\']|f["\'].*mv\s+.*\{.*\}.*["\']',
                    "severity": "HIGH",
                    "description": "f文字列でのコマンド構築は危険です",
                    "recommendation": "subprocess.runで引数リストを使用してください",
                    "cwe_id": "CWE-78",
                },
            ],
            VulnerabilityType.SQL_INJECTION: [
                {
                    "pattern": r'execute\s*\(\s*["\'].*%s.*["\']|query\s*\(\s*["\'].*%s.*["\']',
                    "severity": "HIGH",
                    "description": "SQL文字列結合でインジェクション攻撃の可能性があります",
                    "recommendation": "パラメータ化クエリを使用してください",
                    "cwe_id": "CWE-89",
                },
                {
                    "pattern": r'\+\s*["\'].*SELECT.*["\']|\+\s*["\'].*INSERT.*["\']|\+\s*["\'].*UPDATE.*["\']',
                    "severity": "HIGH",
                    "description": "SQL文字列結合が検出されました",
                    "recommendation": "パラメータ化クエリを使用してください",
                    "cwe_id": "CWE-89",
                },
            ],
            VulnerabilityType.UNSAFE_FILE_OPERATION: [
                {
                    "pattern": r'open\s*\([^)]*["\']w["\'][^)]*\)|open\s*\([^)]*mode\s*=\s*["\']w["\']',
                    "severity": "MEDIUM",
                    "description": "書き込みモードでのファイル操作が検出されました",
                    "recommendation": "ファイルパスの検証と権限チェックを実装してください",
                    "cwe_id": "CWE-73",
                },
                {
                    "pattern": r"shutil\.copy\s*\(|shutil\.move\s*\(",
                    "severity": "MEDIUM",
                    "description": "ファイル操作でパス検証が必要です",
                    "recommendation": "宛先パスの検証を実装してください",
                    "cwe_id": "CWE-73",
                },
            ],
            VulnerabilityType.INSECURE_DESERIALIZATION: [
                {
                    "pattern": r"pickle\.loads?\s*\(|yaml\.load\s*\((?!.*Loader\s*=)|eval\s*\(",
                    "severity": "HIGH",
                    "description": "安全でないデシリアライゼーションが検出されました",
                    "recommendation": "safe_load()やjson.loads()を使用してください",
                    "cwe_id": "CWE-502",
                }
            ],
            VulnerabilityType.XML_YAML_INJECTION: [
                {
                    "pattern": r"yaml\.load\s*\((?!.*Loader\s*=\s*yaml\.SafeLoader)",
                    "severity": "HIGH",
                    "description": "YAML unsafe loadが検出されました",
                    "recommendation": "yaml.safe_load()またはLoader=yaml.SafeLoaderを使用してください",
                    "cwe_id": "CWE-91",
                }
            ],
            VulnerabilityType.INSECURE_RANDOM: [
                {
                    "pattern": r"random\.random\s*\(|random\.choice\s*\(",
                    "severity": "MEDIUM",
                    "description": "暗号学的に安全でない乱数生成器が検出されました",
                    "recommendation": "secrets モジュールを使用してください",
                    "cwe_id": "CWE-338",
                }
            ],
            VulnerabilityType.WEAK_CRYPTO: [
                {
                    "pattern": r"hashlib\.md5\s*\(|hashlib\.sha1\s*\(",
                    "severity": "MEDIUM",
                    "description": "弱いハッシュアルゴリズムが検出されました",
                    "recommendation": "SHA-256以上の強力なハッシュアルゴリズムを使用してください",
                    "cwe_id": "CWE-327",
                }
            ],
        }

    def _print_summary(self, result: SecurityScanResult) -> None:
        """スキャン結果サマリーを表示"""
        self.console.print("\n" + "=" * 60, style="bold")
        self.console.print("🛡️  セキュリティスキャン結果", style="bold blue")
        self.console.print("=" * 60, style="bold")

        self.console.print(f"📁 スキャンファイル数: {result.files_scanned}")
        self.console.print(f"🔴 高リスク脆弱性: {result.high_severity_count}")
        self.console.print(f"🟡 中リスク脆弱性: {result.medium_severity_count}")
        self.console.print(f"🟢 低リスク脆弱性: {result.low_severity_count}")
        self.console.print(f"📊 総脆弱性数: {len(result.vulnerabilities)}\n")

    def generate_report(self, result: SecurityScanResult, output_path: Path | None = None) -> Path:
        """詳細レポートを生成"""
        if not output_path:
            output_path = self.config.get_project_root() / "50_管理資料" / "security_scan_report.json"

        # レポート用データを準備
        report_data = {
            "scan_metadata": {
                "timestamp": self._get_current_timestamp(),
                "files_scanned": result.files_scanned,
                "total_vulnerabilities": len(result.vulnerabilities),
                "severity_breakdown": {
                    "high": result.high_severity_count,
                    "medium": result.medium_severity_count,
                    "low": result.low_severity_count,
                },
            },
            "vulnerabilities": [
                {
                    "type": vuln.vuln_type.value,
                    "file_path": vuln.file_path,
                    "line_number": vuln.line_number,
                    "code_snippet": vuln.code_snippet,
                    "severity": vuln.severity,
                    "description": vuln.description,
                    "recommendation": vuln.recommendation,
                    "cwe_id": vuln.cwe_id,
                    "confidence": vuln.confidence,
                }
                for vuln in result.vulnerabilities
            ],
        }

        # レポート出力
        output_path.parent.mkdir(parents=True, exist_ok=True)
        with output_path.open("w", encoding="utf-8") as f:
            json.dump(report_data, f, ensure_ascii=False, indent=2)

        self.console.print(f"📄 詳細レポートが生成されました: {output_path}", style="success")
        return output_path

    def _get_current_timestamp(self) -> str:
        """現在のタイムスタンプを取得"""
        return project_now().datetime.isoformat()


def main():
    """メイン実行関数"""
    scanner = SecurityVulnerabilityScanner()
    result = scanner.scan_project()
    scanner.generate_report(result)


if __name__ == "__main__":
    main()
