# tests/integration/test_ai_router_feedback_integration.py | 2026-04-22 @ Asia/Taipei
"""AIRouter.feedback_from_aider_events() 整合測試 — 使用真實 awoooi_dev PostgreSQL

替換 tests/test_ai_router_feedback.py 中違反 feedback_no_mock_testing.md 的
FakeRepo / FakeSession mock。

AIRouter.feedback_from_aider_events() 本質是聚合查詢 — 直接用真實 DB 驗證
比 mock DB 更準確，且能抓到 SQL 語法錯誤。

規則: 每個測試後 rollback（由 integration/conftest.py db_session fixture 保證）
禁止 Mock Repository / Session — 直接使用真實 DB 連線。
"""
from __future__ import annotations

from datetime import datetime, timezone, timedelta

import pytest
from sqlalchemy import text

from src.repositories.aider_event_repository import AiderEventRepository

TAIPEI = timezone(timedelta(hours=8))


def _ts(offset_days: int = 0) -> datetime:
    return datetime.now(TAIPEI) - timedelta(days=offset_days)


async def _insert_session(db_session, session_id: str, model: str,
                           repo_cwd: str, has_error: bool = False) -> None:
    """插入一組 session_start + (可選) error event。"""
    repo = AiderEventRepository(db_session)
    await repo.insert(
        session_id=session_id,
        ts=_ts(),
        type_="session_start",
        host="ogt-mac",
        payload={"cwd": repo_cwd, "model": model,
                 "aider_args": [], "aider_pid": 1, "cli_version": "0.86"},
    )
    if has_error:
        await repo.insert(
            session_id=session_id,
            ts=_ts(),
            type_="error",
            host="ogt-mac",
            payload={"cwd": repo_cwd, "model": model,
                     "kind": "api_rate_limit", "message": "429",
                     "context_50chars": ""},
        )


# =============================================================================
# model_stats_since() 聚合正確性
# =============================================================================

class TestModelStatsAggregation:

    @pytest.mark.asyncio
    async def test_empty_db_returns_empty_list(self, db_session):
        """無資料時 model_stats_since 應回傳空 list（不崩潰）。"""
        repo = AiderEventRepository(db_session)
        result = await repo.model_stats_since(days=1)
        # 可能有其他 session 留存（dev DB），但至少型別正確
        assert isinstance(result, list)

    @pytest.mark.asyncio
    async def test_inserted_sessions_appear_in_stats(self, db_session):
        """插入 2 筆 session（1 成功 1 失敗），stats 應正確回傳。"""
        await _insert_session(db_session, "s-ok-001", "elephant-alpha",
                               "/awoooi", has_error=False)
        await _insert_session(db_session, "s-err-001", "elephant-alpha",
                               "/awoooi", has_error=True)
        await db_session.flush()

        repo = AiderEventRepository(db_session)
        result = await repo.model_stats_since(days=1)

        # 找出我們插入的 model
        elephant_rows = [r for r in result
                         if r.get("model") == "elephant-alpha"
                         and r.get("repo") is not None
                         and "/awoooi" in (r.get("repo") or "")]
        assert len(elephant_rows) >= 1

        row = elephant_rows[0]
        assert row["total"] >= 2
        assert 0.0 <= float(row["success_rate"]) <= 1.0

    @pytest.mark.asyncio
    async def test_success_rate_field_is_float(self, db_session):
        """success_rate 欄位必須可轉換為 float（AIRouter 依賴此保證）。"""
        await _insert_session(db_session, "s-float-001", "gemini-pro",
                               "/clawbot", has_error=False)
        await db_session.flush()

        repo = AiderEventRepository(db_session)
        result = await repo.model_stats_since(days=1)
        for row in result:
            # 不應 raise
            _ = float(row.get("success_rate") or 0)

    @pytest.mark.asyncio
    async def test_repo_filter_works(self, db_session):
        """插入兩個不同 cwd 的 session，手動 filter by repo 應只回傳對應資料。"""
        await _insert_session(db_session, "s-awoooi-001", "elephant-alpha",
                               "/awoooi", has_error=False)
        await _insert_session(db_session, "s-other-001", "elephant-alpha",
                               "/other-repo", has_error=True)
        await db_session.flush()

        repo = AiderEventRepository(db_session)
        all_stats = await repo.model_stats_since(days=1)

        # 手動過濾（模擬 AIRouter.feedback_from_aider_events(repo="awoooi")）
        awoooi_rows = [r for r in all_stats if "/awoooi" in (r.get("repo") or "")]
        other_rows = [r for r in all_stats if "/other-repo" in (r.get("repo") or "")]

        # 若兩筆都有資料，它們的 success_rate 應該不同（一個 1.0，一個 0.0）
        # 這裡只確認 filter 邏輯本身不混淆
        for row in awoooi_rows:
            assert "/other-repo" not in (row.get("repo") or "")
        for row in other_rows:
            assert "/awoooi" not in (row.get("repo") or "")


# =============================================================================
# AIRouter.feedback_from_aider_events() error handling（不 mock Session）
# =============================================================================

class TestAIRouterFeedbackDBBehavior:
    """驗證 feedback_from_aider_events() 不崩潰即可（透過 model_stats_since 間接測試）。"""

    @pytest.mark.asyncio
    async def test_model_stats_since_does_not_raise_on_empty(self, db_session):
        """空 DB 時聚合查詢不應拋例外。"""
        repo = AiderEventRepository(db_session)
        try:
            result = await repo.model_stats_since(days=7)
            assert isinstance(result, list)
        except Exception as e:
            pytest.fail(f"model_stats_since raised unexpectedly: {e}")

    @pytest.mark.asyncio
    async def test_daily_pattern_candidates_no_error(self, db_session):
        """daily_pattern_candidates() 不應崩潰。"""
        repo = AiderEventRepository(db_session)
        result = await repo.daily_pattern_candidates(days=1)
        assert isinstance(result, list)
