"""회귀 테스트 — 발견된 버그가 다시 들어오지 못하게 박제.

피드백 #7 인용:
> "버그가 식별되면 수정 후 재발하지 않도록 하는 알고리즘도 필요"

새 버그 발견 → 여기 테스트 추가 → ruff + pytest CI에서 자동 검증.
"""
from __future__ import annotations

import pytest


# ============================================================================
# iter=1 / iter=2: 비동기 lambda + except/loop 변수 캡처 NameError
# (8 + 6 = 14건. 모든 케이스가 `lambda VAR=VAR:` 형식의 default-arg 캡처로 수정됨.)
# ============================================================================

def test_iter1_lambda_default_arg_pattern():
    """`except as e:` 직후 `app.after(..., lambda: ...{e}...)` 패턴은 NameError를 유발.
    수정안 `lambda e=e:` 가 작동하는지 검증.
    """
    queued: list = []

    def fail_then_queue_unfixed():
        try:
            raise ValueError("boom")
        except Exception as e:  # noqa: F841 (의도적 demo — async lambda가 e 캡처)
            queued.append(lambda: f"unfixed: {e}")  # noqa: F821 (의도적 demo)

    def fail_then_queue_fixed():
        try:
            raise ValueError("boom")
        except Exception as e:
            queued.append(lambda e=e: f"fixed: {e}")  # default-arg capture

    fail_then_queue_unfixed()
    fail_then_queue_fixed()

    # Unfixed: 비동기 시점에 `e`가 사라져 NameError.
    with pytest.raises(NameError):
        queued[0]()

    # Fixed: default-arg가 lambda 인스턴스에 락-인.
    assert queued[1]() == "fixed: boom"


def test_iter2_loop_var_capture():
    """B023: 루프 변수가 비동기 lambda에 free-var로 캡처되는 패턴.
    `lambda _m=_m:` 형식이 올바르게 락-인하는지 검증.
    """
    deferred: list = []

    # 미수정 패턴 — 마지막 값으로 모두 통일됨 (PERF401/B023 의도적 demo)
    for x in [1, 2, 3]:
        deferred.append(lambda: x)  # noqa: B023, PERF401 (의도적 demo)
    assert all(f() == 3 for f in deferred), "free-var 캡처는 마지막 x=3로 통일"

    # 수정 패턴 — 각 lambda가 자기 시점의 x를 락-인
    fixed: list = []
    for x in [1, 2, 3]:
        fixed.append(lambda x=x: x)  # noqa: PERF401 (demo 대응)
    assert [f() for f in fixed] == [1, 2, 3]


# ============================================================================
# iter=4: B905 zip strict — sliding window에서 strict=True가 깨지는지 검증
# ============================================================================

def test_sliding_window_strict_false():
    """validate_gate_params.py:254에서 `pairwise(el_chain)` 사용. strict=True였다면
    N과 N-1 길이 차로 ValueError. pairwise는 그 자체로 sliding-pair semantics.
    """
    from itertools import pairwise

    el_chain = [("a", 1.0), ("b", 2.0), ("c", 3.0), ("d", 4.0)]
    pairs = list(pairwise(el_chain))
    assert len(pairs) == len(el_chain) - 1
    assert pairs[0][0] == el_chain[0]
    assert pairs[-1][1] == el_chain[-1]


# ============================================================================
# iter=6: RUF012 ClassVar — class-level mutable default가 인스턴스 간 공유되는지 검증
# ============================================================================

def test_classvar_singleton_pattern():
    """structure_templates.TemplateRegistry는 ClassVar로 명시된 _instance/_templates를
    가진다. 모든 인스턴스가 같은 dict를 공유해야 함.
    """
    try:
        from structure_templates import TemplateRegistry
    except ImportError:
        pytest.skip("structure_templates 미설치 (외부 deps 필요)")

    r1 = TemplateRegistry()
    r2 = TemplateRegistry()
    assert r1 is r2  # __new__ 싱글톤 — 동일 인스턴스
    assert r1._templates is r2._templates  # ClassVar — 동일 dict


def test_gate_parser_struct_layers_classvar():
    """gate_parser.GateParser.STRUCT_LAYERS 가 ClassVar set 인지 (인스턴스 X)."""
    try:
        from gate_parser import GateParser
    except ImportError:
        pytest.skip("gate_parser 미설치")

    p1 = GateParser()
    p2 = GateParser()
    assert p1.STRUCT_LAYERS is p2.STRUCT_LAYERS  # 같은 set 객체 공유
    assert "CS-CONC-Spillway" in p1.STRUCT_LAYERS


# ============================================================================
# iter=6: RUF013 implicit Optional — dxf_geometry._process_entity 시그니처
# ============================================================================

def test_dxf_geometry_inherited_layer_optional_str():
    """dxf_geometry._process_entity의 `inherited_layer` 인자는 명시적 Optional[str]."""
    try:
        from dxf_geometry import extract_structural_geometry
    except ImportError:
        pytest.skip("dxf_geometry 미설치")
    # 타입 자체는 import만 통과하면 OK (런타임 검증 어려움 — annotation 검사로 충분).
    import inspect
    src = inspect.getsource(extract_structural_geometry)
    # 내부 정의된 _process_entity의 시그니처 텍스트 검색
    assert "inherited_layer: str | None = None" in src or \
           "inherited_layer: Optional[str]" in src


# ============================================================================
# iter=7: SIM102 collapsible-if — gate_3d_builder_bpy 진입점 보호
# (apply_blender_patch가 import 시 main()이 우발 실행 안 됨)
# ============================================================================

def test_gate_3d_builder_bpy_no_unexpected_main_on_import():
    """gate_3d_builder_bpy 의 진입점 가드는 `__name__ == "__main__"` AND --params 일 때만."""
    try:
        import sys
        original_argv = sys.argv[:]
        sys.argv = ["test", "--params", "fake.json"]  # --params이 있어도
        try:
            # import만 시도 — main() 호출되면 예외/크래시
            import importlib
            import gate_3d_builder_bpy
            importlib.reload(gate_3d_builder_bpy)
        finally:
            sys.argv = original_argv
    except ModuleNotFoundError:
        pytest.skip("gate_3d_builder_bpy bpy 미설치 (Blender 환경)")
    except SystemExit:
        pytest.fail("import만으로 main()이 실행되었다 — __name__ 가드 손상")


# ============================================================================
# Phase 1A (이번 세션): crash_logger
# ============================================================================

def test_crash_logger_install_idempotent(tmp_path, monkeypatch):
    """install_crash_handlers는 재호출해도 안전 (no-op)."""
    from harness import crash_logger

    # 기존 상태 백업
    orig_excepthook = crash_logger.sys.excepthook
    try:
        log_dir1 = crash_logger.install_crash_handlers()
        log_dir2 = crash_logger.install_crash_handlers()
        assert log_dir1 == log_dir2
        assert log_dir1.exists()
    finally:
        crash_logger.sys.excepthook = orig_excepthook


# ============================================================================
# Phase 1B (이번 세션): TIN colormap 파란색 제거
# ============================================================================

def test_tin_colormap_no_blue():
    """_TIN_EARTH_CMAP은 RGB의 B 채널이 R/G보다 작도록 (즉, 차가운 색이 아님) 보장."""
    try:
        from scanvas_maker import _TIN_EARTH_CMAP
    except ImportError:
        pytest.skip("scanvas_maker 미설치 (GUI deps)")

    import numpy as np
    # 5단계 샘플링 후 각 RGB가 earth-tone인지 확인
    samples = _TIN_EARTH_CMAP(np.linspace(0, 1, 5))[:, :3]  # (5, 3) RGB
    for r, g, b in samples:
        # earth tone: B (파랑)가 R, G 모두보다 작아야 함 — 따뜻한 톤
        assert b <= r, f"파란색 우세 발견: R={r:.2f} G={g:.2f} B={b:.2f}"
        assert b <= g, f"파란색 우세 발견: R={r:.2f} G={g:.2f} B={b:.2f}"


# ============================================================================
# 일반 헬스 — production 모듈 import 가능성
# ============================================================================

@pytest.mark.parametrize("module_name", [
    "dxf_geometry", "filename_classifier", "optional_detector",
    "polygon_reconstructor", "resource_paths",
    "harness.crash_logger", "harness.seed_manager",
    "harness.prompt_registry", "harness.logger",
])
def test_module_imports_without_external_deps(module_name):
    """기본 모듈은 외부 패키지 없이도 import 되거나, 명확한 ImportError를 줘야 한다."""
    import importlib
    try:
        importlib.import_module(module_name)
    except ImportError as e:
        # 외부 deps 부재는 OK — 단 메시지가 도움 되는지
        assert any(token in str(e).lower() for token in
                   ("install", "필요", "required", "missing")), \
            f"{module_name}: ImportError 메시지가 도움 안 됨 ({e})"
