"""SceneValidationService 단위 테스트 — W5 F22 Phase B.6.

SceneImageService에서 분리된 T2I 검증 로직의 실제 동작을 검증한다.
scene_image_service 쪽에는 delegation smoke test만 남긴다.
"""
from __future__ import annotations

from unittest.mock import MagicMock

import pytest

from app.services.scene_validation_service import SceneValidationService


@pytest.fixture
def svc() -> SceneValidationService:
    instance = SceneValidationService.__new__(SceneValidationService)
    instance._db = MagicMock()
    instance._project_id = "p1"
    return instance


def test_validate_scene_missing_file_returns_unchanged(svc, tmp_path):
    """파일 없으면 None/None + 원본 status/notes 유지."""
    validator = MagicMock()
    result = svc.validate_scene(
        validator, str(tmp_path / "missing.png"),
        scene_info={}, entity_names=[],
        status="generated", review_notes="orig",
    )
    assert result == (None, None, "generated", "orig")
    validator.validate_scene_image.assert_not_called()


def test_validate_scene_high_score_keeps_status(svc, tmp_path):
    """score ≥ 60 → status/notes 유지."""
    fp = tmp_path / "ok.png"
    fp.write_bytes(b"fake_png")

    validator = MagicMock()
    validator.validate_scene_image.return_value = {"score": 85, "passed": True, "issues": []}

    score, result_json, status, notes = svc.validate_scene(
        validator, str(fp),
        scene_info={"name": "x"}, entity_names=["char1"],
        status="generated", review_notes="",
    )
    assert score == 85
    assert status == "generated"


def test_validate_scene_low_score_flags_needs_fix(svc, tmp_path):
    """score < 60 → status='needs_fix' + notes에 이슈."""
    fp = tmp_path / "bad.png"
    fp.write_bytes(b"fake_png")

    validator = MagicMock()
    validator.validate_scene_image.return_value = {
        "score": 35, "passed": False, "issues": ["char missing"],
    }

    score, result_json, status, notes = svc.validate_scene(
        validator, str(fp),
        scene_info={}, entity_names=[],
        status="generated", review_notes="",
    )
    assert score == 35
    assert status == "needs_fix"
    assert "char missing" in notes


def test_create_validator_no_openai_key_returns_none(svc, monkeypatch):
    """openai_api_key 비어 있으면 None 반환 (W5 F22 Phase B.7).

    Phase 4 iter 7 follow-up B1: cost policy 도 가드 — full 모드에서도 key 없으면 None.
    """
    import app.services.scene_validation_service as val_mod
    monkeypatch.setattr(val_mod.settings, "scene_lvm_validation_mode", "full")
    monkeypatch.setattr(val_mod.settings, "openai_api_key", "")
    # [2026-08-01] 키 유무의 권위가 브로커로 옮겼다 — 모듈 지역 settings 만
    # 비우면 보조 슬롯이 살아 있어 '있음'이 된다. 브로커의 시야를 비운다.
    monkeypatch.delenv("OPENAI_API_KEY", raising=False)
    monkeypatch.setattr(
        "app.core.config.settings.openai_api_key", "", raising=False)
    monkeypatch.setattr(
        "app.core.config.settings.openai_api_key_secondary", "",
        raising=False)

    assert svc.create_validator() is None


def test_create_validator_with_key_returns_instance(svc, monkeypatch):
    """openai_api_key 있고 scene_lvm_validation_mode='full' 일 때만
    ImageValidator 인스턴스 반환. Phase 4 iter 7 follow-up B1 — cost policy
    가 LVM 2 (legacy validator) 까지 통합 (mode != "full" → None)."""
    import app.services.scene_validation_service as val_mod
    monkeypatch.setattr(val_mod.settings, "scene_lvm_validation_mode", "full")
    monkeypatch.setattr(val_mod.settings, "openai_api_key", "fake-key")

    result = svc.create_validator()
    assert result is not None
    # ImageValidator 인스턴스인지 타입 확인
    from app.modules.image_validator import ImageValidator
    assert isinstance(result, ImageValidator)


def test_validate_scene_exception_returns_none(svc, tmp_path):
    """validator 예외 → None/None + 원본 status/notes 유지."""
    fp = tmp_path / "err.png"
    fp.write_bytes(b"fake_png")

    validator = MagicMock()
    validator.validate_scene_image.side_effect = RuntimeError("boom")

    result = svc.validate_scene(
        validator, str(fp),
        scene_info={}, entity_names=[],
        status="generated", review_notes="orig",
    )
    assert result == (None, None, "generated", "orig")
