"""_try_claim_running atomic INSERT ON CONFLICT — AC-C1, C6, C8, C9, C10, C11 (Block C C1)."""
from unittest.mock import MagicMock

from app.core.step_runner import StepRunner


def _make_runner() -> StepRunner:
    runner = StepRunner.__new__(StepRunner)
    runner.step_id = "test"
    runner.project_id = "p1"
    runner.episode_id = "e1"
    runner.run_id = "r-new"
    runner.db = MagicMock()
    return runner


def test_first_run_inserts_new_row():
    """AC-C6: row 없을 때 INSERT 경로로 claim 성공 (RETURNING 한 row)."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = ("r-new",)
    runner.db.execute.return_value = result_mock

    ok = runner._try_claim_running()
    assert ok is True


def test_existing_running_returns_false_when_no_steal():
    """기존 row status='running' 일 때 → claim 실패 (allow_stale_steal=False)."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = None  # WHERE 조건 미충족
    runner.db.execute.return_value = result_mock

    ok = runner._try_claim_running()
    assert ok is False


def test_stale_steal_with_expected_match():
    """AC-C8: STALE_RUNNING_RECOVERY 시 expected_started_at + expected_run_id bind."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = ("r-new",)
    runner.db.execute.return_value = result_mock

    ok = runner._try_claim_running(
        allow_stale_steal=True,
        expected_started_at="2026-05-07T10:00:00+00:00",
        expected_run_id="r-old",
    )
    assert ok is True
    bind_params = runner.db.execute.call_args[0][1]
    assert bind_params["allow_stale_steal"] is True
    assert bind_params["expected_started_at"] == "2026-05-07T10:00:00+00:00"
    assert bind_params["expected_run_id"] == "r-old"


def test_no_sql_cast_in_query():
    """AC-C10: claim SQL 에 ::timestamptz / ::interval cast 0건."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = None
    runner.db.execute.return_value = result_mock

    runner._try_claim_running()

    sql_text = str(runner.db.execute.call_args[0][0])
    assert "::timestamptz" not in sql_text
    assert "::interval" not in sql_text


def test_uses_fetchone_not_rowcount():
    """AC-C11: result.fetchone() is not None 으로 판정 (driver 차이 회피)."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = ("r-new",)
    runner.db.execute.return_value = result_mock

    ok = runner._try_claim_running()
    assert ok is True
    result_mock.fetchone.assert_called_once()


def test_insert_columns_includes_all_not_null():
    """AC-C9: INSERT 컬럼 목록에 NOT NULL 모두 명시 — id/project_id/episode_id/step_id/run_id/status/started_at/created_at/updated_at."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = None
    runner.db.execute.return_value = result_mock

    runner._try_claim_running()
    sql_text = str(runner.db.execute.call_args[0][0])
    for col in ("id", "project_id", "episode_id", "step_id", "run_id",
                "status", "started_at", "created_at", "updated_at"):
        assert col in sql_text, f"INSERT 에 {col!r} 누락"
