"""
task-1947: blog_writer.py codex/gpt provider → codex-companion.mjs 전환 단위 테스트
테스터: 모리건 (개발3팀)
"""

import os
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch


# blog_writer.py 경로 추가
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

COMPANION_PATH = (
    "/home/jay/.claude/plugins/cache/openai-codex/codex/1.0.3/scripts/codex-companion.mjs"
)
BLOG_WRITER_PATH = Path(__file__).parent.parent / "dashboard" / "blog_writer.py"


# ---------------------------------------------------------------------------
# 공통 픽스처: _background_blog_generate 실행을 위한 mock 환경
# ---------------------------------------------------------------------------

def _make_mock_result(returncode=0, stdout="생성된 블로그 글 내용입니다.", stderr=""):
    mock = MagicMock()
    mock.returncode = returncode
    mock.stdout = stdout
    mock.stderr = stderr
    return mock


def _run_blog_generate(model: str, mock_run_result, tmp_path: Path):
    """_background_blog_generate를 mock 환경에서 실행하는 헬퍼."""
    status_path = tmp_path / "status.json"
    lock_path = tmp_path / "lock"
    lock_path.touch()

    with (
        patch("subprocess.run", return_value=mock_run_result) as mock_run,
        patch(
            "dashboard.blog_writer._build_naver_blog_prompt",
            return_value="테스트 프롬프트",
        ),
        patch("dashboard.blog_writer._update_blog_write_status"),
        patch("dashboard.blog_writer._BLOG_HISTORY_DB", tmp_path / "blog.db"),
        patch(
            "dashboard.blog_writer._IMAGE_OUTPUT_BASE", tmp_path / "images"
        ),
        patch("sqlite3.connect"),
    ):
        from dashboard.blog_writer import _background_blog_generate

        _background_blog_generate(
            keywords=["테스트키워드"],
            additional_content="",
            tone="정보형",
            model=model,
            status_path=status_path,
            lock_path=lock_path,
        )
        return mock_run


# ---------------------------------------------------------------------------
# 테스트 1: codex provider → node + companion_path 호출 확인
# ---------------------------------------------------------------------------

def test_codex_provider_uses_companion(tmp_path):
    """codex provider가 subprocess.run을 ['node', companion_path, 'task', ...] 형태로 호출하는지 확인."""
    mock_result = _make_mock_result(stdout="codex 생성 결과")
    mock_run = _run_blog_generate("codex", mock_result, tmp_path)

    assert mock_run.called, "subprocess.run이 호출되지 않았습니다"

    call_args = mock_run.call_args
    cmd = call_args[0][0]  # 첫 번째 위치 인자 (명령어 리스트)

    assert cmd[0] == "node", f"첫 번째 인자가 'node'여야 합니다. 실제: {cmd[0]}"
    assert cmd[1] == COMPANION_PATH, (
        f"두 번째 인자가 companion_path여야 합니다. 실제: {cmd[1]}"
    )
    assert cmd[2] == "task", f"세 번째 인자가 'task'여야 합니다. 실제: {cmd[2]}"
    assert len(cmd) == 4, f"명령어 인자 수가 4개여야 합니다. 실제: {len(cmd)}"
    # codex exec 패턴이 없어야 함
    assert "codex" not in cmd[:2], "명령어에 'codex exec' 패턴이 포함되면 안 됩니다"
    assert "exec" not in cmd, "명령어에 'exec' 인자가 포함되면 안 됩니다"


# ---------------------------------------------------------------------------
# 테스트 2: gpt provider → node + companion_path 호출 확인
# ---------------------------------------------------------------------------

def test_gpt_provider_uses_companion(tmp_path):
    """gpt(레거시) provider도 codex-companion.mjs를 통해 호출되는지 확인."""
    mock_result = _make_mock_result(stdout="gpt 생성 결과")
    mock_run = _run_blog_generate("gpt", mock_result, tmp_path)

    assert mock_run.called, "subprocess.run이 호출되지 않았습니다"

    call_args = mock_run.call_args
    cmd = call_args[0][0]

    assert cmd[0] == "node", f"첫 번째 인자가 'node'여야 합니다. 실제: {cmd[0]}"
    assert cmd[1] == COMPANION_PATH, (
        f"두 번째 인자가 companion_path여야 합니다. 실제: {cmd[1]}"
    )
    assert cmd[2] == "task", f"세 번째 인자가 'task'여야 합니다. 실제: {cmd[2]}"
    assert len(cmd) == 4, f"명령어 인자 수가 4개여야 합니다. 실제: {len(cmd)}"
    assert "exec" not in cmd, "명령어에 'exec' 인자가 포함되면 안 됩니다"


# ---------------------------------------------------------------------------
# 테스트 3: 소스 코드 정적 검증 — 'codex exec' 패턴 부재 확인
# ---------------------------------------------------------------------------

def test_no_codex_exec_pattern():
    """blog_writer.py 소스 코드에 'codex exec' 또는 ['codex', 'exec'] 패턴이 없는지 정적 검증."""
    assert BLOG_WRITER_PATH.exists(), f"blog_writer.py 파일이 존재하지 않습니다: {BLOG_WRITER_PATH}"

    source = BLOG_WRITER_PATH.read_text(encoding="utf-8")

    # 'codex exec' 문자열 패턴 검사
    assert "codex exec" not in source, (
        "소스 코드에 'codex exec' 문자열이 존재합니다. companion.mjs 전환 후에는 있으면 안 됩니다."
    )

    # ["codex", "exec"] 리스트 패턴 검사
    assert '"codex", "exec"' not in source and "'codex', 'exec'" not in source, (
        "소스 코드에 [\"codex\", \"exec\"] 패턴이 존재합니다. companion.mjs 전환 후에는 있으면 안 됩니다."
    )

    # companion_path가 소스에 존재하는지 확인 (전환 확인)
    assert COMPANION_PATH in source, (
        f"소스 코드에 companion_path({COMPANION_PATH})가 없습니다. 전환이 적용되지 않았을 수 있습니다."
    )

    # 'node'가 companion 호출에 사용되는지 확인
    assert '"node"' in source or "'node'" in source, (
        "소스 코드에 'node' 명령어 문자열이 없습니다. companion.mjs 전환이 올바르지 않을 수 있습니다."
    )


# ---------------------------------------------------------------------------
# 테스트 4: companion 실패 시 ValueError 발생 확인
# ---------------------------------------------------------------------------

def test_companion_error_handling(tmp_path):
    """codex companion이 returncode != 0으로 실패할 때 status가 failed로 기록되는지 확인."""
    mock_result = _make_mock_result(returncode=1, stdout="", stderr="companion 실행 오류 발생")

    status_path = tmp_path / "status.json"
    lock_path = tmp_path / "lock"
    lock_path.touch()

    captured_status_calls = []

    def capture_status(_path, data):
        captured_status_calls.append(data)

    with (
        patch("subprocess.run", return_value=mock_result),
        patch(
            "dashboard.blog_writer._build_naver_blog_prompt",
            return_value="테스트 프롬프트",
        ),
        patch(
            "dashboard.blog_writer._update_blog_write_status",
            side_effect=capture_status,
        ),
        patch("dashboard.blog_writer._BLOG_HISTORY_DB", tmp_path / "blog.db"),
        patch("dashboard.blog_writer._IMAGE_OUTPUT_BASE", tmp_path / "images"),
        patch("sqlite3.connect"),
    ):
        from dashboard.blog_writer import _background_blog_generate

        # _background_blog_generate는 예외를 내부에서 catch하므로 직접 예외가 전파되지 않음
        _background_blog_generate(
            keywords=["테스트키워드"],
            additional_content="",
            tone="정보형",
            model="codex",
            status_path=status_path,
            lock_path=lock_path,
        )

    # status "failed"가 기록되어야 함
    failed_calls = [c for c in captured_status_calls if c.get("status") == "failed"]
    assert len(failed_calls) > 0, (
        "companion 실패 시 status='failed'로 _update_blog_write_status가 호출되어야 합니다"
    )

    # 에러 메시지에 Codex 관련 내용이 포함되어야 함
    error_msg = failed_calls[0].get("error", "")
    assert "Codex" in error_msg or "CLI" in error_msg or "오류" in error_msg, (
        f"에러 메시지에 Codex 오류 관련 내용이 없습니다. 실제 에러: {error_msg}"
    )
