# tests/integration/test_aider_event_processor_integration.py
# 2026-04-22 @ Asia/Taipei: 從 tests/test_aider_event_processor.py 遷移
#   需真實 PostgreSQL 的三個測試移到 integration 層，符合 CI 架構：
#   tests/         — 單元測試（無 DB），CI 直接執行
#   tests/integration/ — 整合測試（真實 DB），CI --ignore 後由 K8s E2E 覆蓋
"""Integration tests for AiderEventProcessor — 真實 DB + mock 外部服務。"""
import json
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, MagicMock

import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine

from src.repositories.aider_event_repository import AiderEventRepository
from src.workers.aider_event_processor import AiderEventProcessor

TAIPEI = timezone(timedelta(hours=8))


def _payload_dict(type_="error"):
    base = {
        "ts": datetime.now(TAIPEI).isoformat(),
        "session_id": "test-s1", "host": "ogt-mac",
        "type": type_,
        "payload": {"cwd": "/r", "model": "elephant-alpha",
                    "kind": "api_rate_limit", "message": "429",
                    "context_50chars": ""},
    }
    if type_ == "session_start":
        base["payload"] = {"cwd": "/r", "model": "m", "aider_args": [],
                           "aider_pid": 1, "cli_version": "0"}
    return base


# =============================================================================
# real_factory fixture — 真實 DB，測試後 rollback
# =============================================================================

@pytest_asyncio.fixture
async def real_factory(db_session):
    """用 integration conftest 的 db_session 建立 _process_one 可用的 factory。"""
    class _SingleSessionFactory:
        def __call__(self):
            return _SessionCtx(db_session)

    class _SessionCtx:
        def __init__(self, sess): self._sess = sess
        async def __aenter__(self): return self._sess
        async def __aexit__(self, *a): pass

    return _SingleSessionFactory()


# =============================================================================
# Tests
# =============================================================================

@pytest.mark.asyncio
@pytest.mark.integration
async def test_process_one_error_event_creates_incident_and_writes_db(real_factory, db_session):
    fake_incident = MagicMock()
    fake_incident.incident_id = "inc-123"
    fake_engine = MagicMock()
    fake_engine.process_signal = AsyncMock(return_value=fake_incident)
    fake_r = MagicMock()
    fake_r.xack = AsyncMock()

    proc = AiderEventProcessor()
    data = {b"payload": json.dumps(_payload_dict("error")).encode()}

    import unittest.mock as um
    with um.patch("src.workers.aider_event_processor.get_incident_engine", return_value=fake_engine), \
         um.patch("src.workers.aider_event_processor.get_worker_redis", return_value=fake_r):
        await proc._process_one("stream", "1-0", data, _session_factory=real_factory)

    assert fake_engine.process_signal.called
    fake_r.xack.assert_called_once()

    repo = AiderEventRepository(db_session)
    count = await repo.count_by_session("test-s1")
    assert count == 1


@pytest.mark.asyncio
@pytest.mark.integration
async def test_process_one_session_start_no_incident(real_factory, db_session):
    fake_engine = MagicMock()
    fake_engine.process_signal = AsyncMock()
    fake_r = MagicMock()
    fake_r.xack = AsyncMock()

    proc = AiderEventProcessor()
    data = {b"payload": json.dumps(_payload_dict("session_start")).encode()}

    import unittest.mock as um
    with um.patch("src.workers.aider_event_processor.get_incident_engine", return_value=fake_engine), \
         um.patch("src.workers.aider_event_processor.get_worker_redis", return_value=fake_r):
        await proc._process_one("stream", "1-0", data, _session_factory=real_factory)

    assert not fake_engine.process_signal.called
    fake_r.xack.assert_called_once()

    repo = AiderEventRepository(db_session)
    count = await repo.count_by_session("test-s1")
    assert count == 1


@pytest.mark.asyncio
@pytest.mark.integration
async def test_incident_failure_still_writes_db(real_factory, db_session):
    fake_engine = MagicMock()
    fake_engine.process_signal = AsyncMock(side_effect=RuntimeError("engine down"))
    fake_r = MagicMock()
    fake_r.xack = AsyncMock()

    proc = AiderEventProcessor()
    data = {b"payload": json.dumps(_payload_dict("error")).encode()}

    import unittest.mock as um
    with um.patch("src.workers.aider_event_processor.get_incident_engine", return_value=fake_engine), \
         um.patch("src.workers.aider_event_processor.get_worker_redis", return_value=fake_r):
        await proc._process_one("stream", "1-0", data, _session_factory=real_factory)

    fake_r.xack.assert_called_once()

    repo = AiderEventRepository(db_session)
    count = await repo.count_by_session("test-s1")
    assert count == 1
