"""gemini_nb2_generate.py의 순수 함수 및 상수에 대한 단위 테스트."""

import json
import sys
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import pytest

sys.path.insert(0, str(Path(__file__).parent))

from gemini_nb2_generate import (  # noqa: E402
    GEMINI_API_BASE,
    GEMINI_SCOPE,
    MODEL_ID,
    OUTPUT_DIR,
    RESULTS_JSON,
    SCENARIOS,
    log_error,
)

# ---------------------------------------------------------------------------
# 상수 검증
# ---------------------------------------------------------------------------


class TestConstants:
    def test_model_id_is_nano_banana2(self) -> None:
        """모델 ID가 Nano Banana 2 (gemini-3.1-flash-image-preview)이다."""
        assert MODEL_ID == "gemini-3.1-flash-image-preview"

    def test_gemini_api_base_is_valid_url(self) -> None:
        """Gemini API 기본 URL이 올바른 형식이다."""
        assert GEMINI_API_BASE.startswith("https://")
        assert "generativelanguage.googleapis.com" in GEMINI_API_BASE

    def test_gemini_scope_is_generative_language(self) -> None:
        """Gemini API 스코프가 generative-language이다."""
        assert "generative-language" in GEMINI_SCOPE

    def test_output_dir_is_v2_gemini_nb2(self) -> None:
        """출력 디렉토리가 v2-gemini-nb2 폴더이다."""
        assert "v2-gemini-nb2" in str(OUTPUT_DIR)

    def test_results_json_is_in_output_dir(self) -> None:
        """results.json이 OUTPUT_DIR 하위에 있다."""
        assert RESULTS_JSON.parent == OUTPUT_DIR


# ---------------------------------------------------------------------------
# SCENARIOS 검증
# ---------------------------------------------------------------------------


class TestScenarios:
    def test_has_three_scenarios(self) -> None:
        """SCENARIOS dict에 3개의 시나리오가 존재한다."""
        assert len(SCENARIOS) == 3

    def test_scenario_keys_are_a_b_c(self) -> None:
        """SCENARIOS의 키는 A, B, C 이다."""
        assert set(SCENARIOS.keys()) == {"A", "B", "C"}

    def test_all_values_are_non_empty_strings(self) -> None:
        """모든 시나리오 값은 비어 있지 않은 문자열이다."""
        for key, value in SCENARIOS.items():
            assert isinstance(value, str), f"시나리오 {key}의 값이 str이 아닙니다."
            assert len(value) > 0, f"시나리오 {key}의 값이 비어 있습니다."

    def test_scenario_a_contains_recruiting_keywords(self) -> None:
        """시나리오 A에 리크루팅 관련 키워드가 포함된다."""
        assert "recruiting" in SCENARIOS["A"].lower() or "커리어" in SCENARIOS["A"]

    def test_scenario_b_contains_branding_keywords(self) -> None:
        """시나리오 B에 브랜딩 관련 키워드가 포함된다."""
        assert "branding" in SCENARIOS["B"].lower() or "보험" in SCENARIOS["B"]

    def test_scenario_c_contains_motivational_keywords(self) -> None:
        """시나리오 C에 동기부여 관련 키워드가 포함된다."""
        assert "motivational" in SCENARIOS["C"].lower() or "기회" in SCENARIOS["C"]


# ---------------------------------------------------------------------------
# log_error
# ---------------------------------------------------------------------------


class TestLogError:
    def test_log_error_creates_file(self, tmp_path: Path, monkeypatch: Any) -> None:
        """log_error 호출 시 에러 로그 파일이 생성된다."""
        import gemini_nb2_generate as mod

        errors_log = tmp_path / "errors.log"
        monkeypatch.setattr(mod, "ERRORS_LOG", errors_log)

        mod.log_error("테스트 에러 메시지")

        assert errors_log.exists()

    def test_log_error_writes_message(self, tmp_path: Path, monkeypatch: Any) -> None:
        """log_error 호출 시 메시지가 로그 파일에 기록된다."""
        import gemini_nb2_generate as mod

        errors_log = tmp_path / "errors.log"
        monkeypatch.setattr(mod, "ERRORS_LOG", errors_log)

        mod.log_error("에러 내용: 테스트")

        content = errors_log.read_text(encoding="utf-8")
        assert "에러 내용: 테스트" in content

    def test_log_error_appends(self, tmp_path: Path, monkeypatch: Any) -> None:
        """log_error를 두 번 호출하면 두 줄이 기록된다."""
        import gemini_nb2_generate as mod

        errors_log = tmp_path / "errors.log"
        monkeypatch.setattr(mod, "ERRORS_LOG", errors_log)

        mod.log_error("첫 번째 에러")
        mod.log_error("두 번째 에러")

        content = errors_log.read_text(encoding="utf-8")
        assert "첫 번째 에러" in content
        assert "두 번째 에러" in content

    def test_log_error_includes_timestamp(self, tmp_path: Path, monkeypatch: Any) -> None:
        """log_error 로그에 타임스탬프가 포함된다."""
        import gemini_nb2_generate as mod

        errors_log = tmp_path / "errors.log"
        monkeypatch.setattr(mod, "ERRORS_LOG", errors_log)

        mod.log_error("타임스탬프 확인")

        content = errors_log.read_text(encoding="utf-8")
        # ISO 형식 타임스탬프 패턴: [2026-...]
        assert "[2026-" in content or "[202" in content


# ---------------------------------------------------------------------------
# get_gcloud_access_token (subprocess mock)
# ---------------------------------------------------------------------------


class TestGetGcloudAccessToken:
    def test_returns_token_on_success(self) -> None:
        """gcloud_auth.get_access_token()이 gcloud CLI fallback으로 토큰을 반환한다."""
        import gcloud_auth

        mock_result = MagicMock()
        mock_result.stdout = "fake-token-12345\n"

        with patch.object(gcloud_auth, "_token_cache", {"token": None, "expiry": None}):
            with patch("google.auth.default", side_effect=Exception("ADC 없음")):
                with patch("subprocess.run", return_value=mock_result) as mock_run:
                    token = gcloud_auth.get_access_token()

        assert token == "fake-token-12345"
        mock_run.assert_called_once()

    def test_raises_on_empty_token(self) -> None:
        """gcloud CLI fallback에서 빈 토큰 반환 시 RuntimeError를 발생시킨다."""
        import gcloud_auth

        mock_result = MagicMock()
        mock_result.stdout = "   \n"

        with patch.object(gcloud_auth, "_token_cache", {"token": None, "expiry": None}):
            with patch("google.auth.default", side_effect=Exception("ADC 없음")):
                with patch("subprocess.run", return_value=mock_result):
                    with pytest.raises(RuntimeError, match="빈 토큰"):
                        gcloud_auth.get_access_token()
