"""사전 실행 보안 스캔 모듈.

명령어 실행 전 정적 패턴 기반 스캔을 수행.
approval.py와 통합하여 위험 레벨에 따라 즉시 차단 또는 경고.

통합 정책:
  - approval critical/high → 즉시 block (정적 패턴 스킵)
  - approval medium        → warn + 정적 패턴도 실행
  - approval 없으면        → 정적 패턴만 실행

Usage:
    from utils.pre_exec_scan import scan_command
    verdict = scan_command("rm -rf /")
    if verdict.action == "block":
        print(verdict.summary)
"""

import re
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from utils.pre_exec_patterns import EXEC_THREAT_PATTERNS

# approval 선택 import (없어도 정상 동작)
_approval_check_command: Callable[[str], object] | None = None
try:
    from utils.approval import check_command as _approval_check_command  # type: ignore[assignment]

    _APPROVAL_AVAILABLE = True
except ImportError:
    _APPROVAL_AVAILABLE = False

# 즉시 block할 approval 위험 레벨
_BLOCK_LEVELS: frozenset[str] = frozenset({"critical", "high"})
# warn으로 처리할 approval 위험 레벨
_WARN_LEVELS: frozenset[str] = frozenset({"medium"})


# ---------------------------------------------------------------------------
# 데이터클래스
# ---------------------------------------------------------------------------


@dataclass
class ScanVerdict:
    """사전 실행 스캔 판정 결과."""

    action: str  # "allow" | "warn" | "block"
    findings: list[dict] = field(default_factory=list)
    summary: str = ""
    scanner: str = "static"  # "static" | "disabled"


# ---------------------------------------------------------------------------
# 내부 함수
# ---------------------------------------------------------------------------


def _scan_static(command: str) -> ScanVerdict:
    """정적 패턴 기반 스캔."""
    if not command:
        return ScanVerdict(action="allow", findings=[], summary="명령어 없음", scanner="static")

    findings: list[dict] = []

    for pattern, threat_id, description in EXEC_THREAT_PATTERNS:
        m = re.search(pattern, command, re.IGNORECASE)
        if m:
            findings.append(
                {
                    "threat_id": threat_id,
                    "description": description,
                    "matched": m.group(0)[:200],
                }
            )

    if not findings:
        return ScanVerdict(action="allow", findings=[], summary="위협 없음", scanner="static")

    # DEST-*, ENV-* 는 block, 나머지는 warn
    block_prefixes = ("DEST-", "ENV-", "TERM-", "HOMO-")
    should_block = any(f["threat_id"].startswith(block_prefixes) for f in findings)

    action = "block" if should_block else "warn"
    threat_ids = ", ".join(f["threat_id"] for f in findings)
    summary = f"위협 탐지: {threat_ids}"

    return ScanVerdict(action=action, findings=findings, summary=summary, scanner="static")


def _integrate_approval(command: str) -> str | None:
    """기존 approval.py와 통합.

    Returns:
        approval의 risk_level 문자열, 또는 approval 미사용 시 None.
    """
    if not _APPROVAL_AVAILABLE or _approval_check_command is None:
        return None

    result = _approval_check_command(command)  # type: ignore[union-attr]
    if result.is_safe:  # type: ignore[union-attr]
        return "safe"
    return result.risk_level  # type: ignore[union-attr]


# ---------------------------------------------------------------------------
# 공개 API
# ---------------------------------------------------------------------------


def scan_command(command: str) -> ScanVerdict:
    """명령어 사전 실행 보안 스캔.

    approval 통합 → 정적 패턴 순서로 스캔하여 ScanVerdict 반환.
    """
    if not command:
        return ScanVerdict(action="allow", findings=[], summary="명령어 없음", scanner="static")

    # 1단계: approval.py 통합 검사
    approval_level = _integrate_approval(command)

    if approval_level in _BLOCK_LEVELS:
        return ScanVerdict(
            action="block",
            findings=[
                {"threat_id": "APPROVAL", "description": f"approval 판정: {approval_level}", "matched": command[:200]}
            ],
            summary=f"approval 차단: {approval_level}",
            scanner="static",
        )

    # 2단계: 정적 패턴 스캔
    static_verdict = _scan_static(command)

    # approval이 medium이면 warn으로 격상
    if approval_level in _WARN_LEVELS and static_verdict.action == "allow":
        return ScanVerdict(
            action="warn",
            findings=static_verdict.findings,
            summary=f"approval 경고: {approval_level}",
            scanner="static",
        )

    return static_verdict
