# ruff: noqa: INP001
"""Scheduled Forgejo issue sync queue tests."""

from __future__ import annotations

from uuid import uuid4

import pytest
from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine
from sqlmodel import SQLModel, col, select
from sqlmodel.ext.asyncio.session import AsyncSession

from app import models as _models
from app.models.forgejo_connections import ForgejoConnection
from app.models.forgejo_repositories import ForgejoRepository
from app.models.organizations import Organization
from app.services import forgejo_sync_queue
from app.services.forgejo_sync_queue import (
    enqueue_forgejo_issue_sync,
    process_forgejo_sync_queue_task,
    sync_active_forgejo_repositories,
)
from app.services.queue import QueuedTask

_MODEL_REGISTRY = _models


async def _make_engine() -> AsyncEngine:
    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    async with engine.begin() as conn:
        await conn.run_sync(SQLModel.metadata.create_all)
    return engine


async def _seed_repositories(
    session: AsyncSession,
) -> tuple[ForgejoRepository, ForgejoRepository, ForgejoRepository]:
    organization_id = uuid4()
    connection_id = uuid4()
    session.add(Organization(id=organization_id, name=f"org-{organization_id}"))
    session.add(
        ForgejoConnection(
            id=connection_id,
            organization_id=organization_id,
            name="Forgejo",
            base_url="https://forgejo.example.local",
        )
    )
    active_repo = ForgejoRepository(
        id=uuid4(),
        organization_id=organization_id,
        connection_id=connection_id,
        owner="openclaw",
        repo="pipeline",
        active=True,
    )
    failing_repo = ForgejoRepository(
        id=uuid4(),
        organization_id=organization_id,
        connection_id=connection_id,
        owner="openclaw",
        repo="broken",
        active=True,
    )
    inactive_repo = ForgejoRepository(
        id=uuid4(),
        organization_id=organization_id,
        connection_id=connection_id,
        owner="openclaw",
        repo="inactive",
        active=False,
    )
    session.add(active_repo)
    session.add(failing_repo)
    session.add(inactive_repo)
    await session.commit()
    return active_repo, failing_repo, inactive_repo


def test_enqueue_forgejo_issue_sync_uses_singleton_delayed_task(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    captured: dict[str, object] = {}

    def _fake_enqueue_with_delay(
        task: QueuedTask,
        queue_name: str,
        *,
        delay_seconds: float,
        redis_url: str | None = None,
    ) -> bool:
        captured["task"] = task
        captured["queue_name"] = queue_name
        captured["delay_seconds"] = delay_seconds
        captured["redis_url"] = redis_url
        return True

    monkeypatch.setattr(forgejo_sync_queue.settings, "forgejo_sync_enabled", True)
    monkeypatch.setattr(forgejo_sync_queue.settings, "forgejo_sync_interval_seconds", 3600)
    monkeypatch.setattr(
        "app.services.forgejo_sync_queue.enqueue_task_with_delay",
        _fake_enqueue_with_delay,
    )

    assert enqueue_forgejo_issue_sync(delay_seconds=30) is True
    task = captured["task"]
    assert isinstance(task, QueuedTask)
    assert task.task_type == "forgejo_issue_sync_all"
    assert task.payload == {"scope": "active_repositories"}
    assert task.created_at.isoformat() == "1970-01-01T00:00:00+00:00"
    assert captured["delay_seconds"] == 30


def test_enqueue_forgejo_issue_sync_respects_disabled_setting(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(forgejo_sync_queue.settings, "forgejo_sync_enabled", False)
    monkeypatch.setattr(
        "app.services.forgejo_sync_queue.enqueue_task_with_delay",
        lambda *args, **kwargs: pytest.fail("disabled sync should not enqueue"),
    )

    assert enqueue_forgejo_issue_sync(delay_seconds=30) is False


@pytest.mark.asyncio
async def test_sync_active_forgejo_repositories_syncs_active_and_records_errors(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    calls: list[str] = []

    class _FakeIssueSyncService:
        def __init__(self, *, session: AsyncSession, organization_id: object) -> None:
            del session, organization_id

        async def sync_repository_issues(self, *, repository_id: object) -> dict[str, int]:
            calls.append(str(repository_id))
            if repository_id == failing_repo.id:
                raise RuntimeError("Forgejo unavailable")
            return {"created": 2, "updated": 3, "open": 4, "closed": 1, "total": 5}

    engine = await _make_engine()
    session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
    try:
        async with session_maker() as session:
            active_repo, failing_repo, inactive_repo = await _seed_repositories(session)
            monkeypatch.setattr(
                "app.services.forgejo_sync_queue.IssueSyncService",
                _FakeIssueSyncService,
            )

            totals = await sync_active_forgejo_repositories(session)

            assert totals == {
                "repositories": 2,
                "succeeded": 1,
                "failed": 1,
                "created": 2,
                "updated": 3,
                "open": 4,
                "closed": 1,
                "total": 5,
            }
            assert calls == [str(active_repo.id), str(failing_repo.id)]

        async with session_maker() as session:
            stored_failing_repo = (
                await session.exec(
                    select(ForgejoRepository).where(col(ForgejoRepository.id) == failing_repo.id)
                )
            ).one()
            stored_inactive_repo = (
                await session.exec(
                    select(ForgejoRepository).where(col(ForgejoRepository.id) == inactive_repo.id)
                )
            ).one()
            assert stored_failing_repo.last_sync_error == "Forgejo unavailable"
            assert stored_inactive_repo.last_sync_error is None
    finally:
        await engine.dispose()


@pytest.mark.asyncio
async def test_process_forgejo_sync_queue_task_reschedules_after_run(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    called: list[str] = []

    async def _fake_sync_active_repositories(session: AsyncSession) -> dict[str, int]:
        del session
        called.append("sync")
        return {
            "repositories": 0,
            "succeeded": 0,
            "failed": 0,
            "created": 0,
            "updated": 0,
            "open": 0,
            "closed": 0,
            "total": 0,
        }

    class _FakeSessionMaker:
        async def __aenter__(self) -> AsyncSession:
            return None  # type: ignore[return-value]

        async def __aexit__(self, *args: object) -> None:
            return None

    monkeypatch.setattr(forgejo_sync_queue.settings, "forgejo_sync_enabled", True)
    monkeypatch.setattr(forgejo_sync_queue.settings, "forgejo_sync_interval_seconds", 12)
    monkeypatch.setattr("app.services.forgejo_sync_queue.async_session_maker", _FakeSessionMaker)
    monkeypatch.setattr(
        "app.services.forgejo_sync_queue.sync_active_forgejo_repositories",
        _fake_sync_active_repositories,
    )
    monkeypatch.setattr(
        "app.services.forgejo_sync_queue.schedule_next_forgejo_issue_sync",
        lambda: called.append("reschedule") or True,
    )

    await process_forgejo_sync_queue_task(
        QueuedTask(
            task_type="forgejo_issue_sync_all",
            payload={"scope": "active_repositories"},
            created_at=forgejo_sync_queue._SINGLETON_CREATED_AT,
        )
    )

    assert called == ["sync", "reschedule"]
