from __future__ import annotations

from types import SimpleNamespace
from uuid import uuid4

import pytest

from src.domain.storage_ingest import background
from src.domain.storage_ingest.models import StorageIngestJobStatus
from src.workers.tasks import storage_ingest as ingest_task


class _FakeResult:
    def __init__(self, rows):
        self._rows = rows

    def all(self):
        return self._rows

    def scalars(self):
        return self


class _FakeDb:
    def __init__(self, rows):
        self._rows = rows

    async def execute(self, stmt):
        return _FakeResult(self._rows)


class _FakeSessionFactory:
    def __init__(self, rows):
        self._rows = rows

    async def __aenter__(self):
        return _FakeDb(self._rows)

    async def __aexit__(self, exc_type, exc, tb):
        return False


@pytest.mark.asyncio
async def test_schedule_stale_pending_jobs_enqueues_recovery(monkeypatch: pytest.MonkeyPatch):
    scheduled: list[tuple[str, int | None]] = []
    pending = SimpleNamespace(
        id=uuid4(),
        status=StorageIngestJobStatus.PENDING.value,
        error=None,
    )
    retryable = SimpleNamespace(
        id=uuid4(),
        status=StorageIngestJobStatus.FAILED.value,
        error="staging_file_missing",
    )
    fatal = SimpleNamespace(
        id=uuid4(),
        status=StorageIngestJobStatus.FAILED.value,
        error="connector_r2_credentials_incomplete",
    )

    monkeypatch.setattr(
        background,
        "get_settings",
        lambda: SimpleNamespace(storage_ingest_worker_grace_seconds=30),
    )
    monkeypatch.setattr(
        background,
        "async_session_factory",
        lambda: _FakeSessionFactory([pending, retryable, fatal]),
    )
    monkeypatch.setattr(
        background,
        "schedule_storage_ingest_fallback",
        lambda job_id, delay_seconds=None: scheduled.append((job_id, delay_seconds)),
    )

    count = await background._schedule_stale_pending_jobs()

    assert count == 2
    assert {job_id for job_id, _ in scheduled} == {str(pending.id), str(retryable.id)}
    assert all(delay == 1 for _, delay in scheduled)


@pytest.mark.asyncio
async def test_process_skips_claim_when_staging_not_on_this_host(monkeypatch: pytest.MonkeyPatch):
    job_id = uuid4()
    job = SimpleNamespace(
        id=job_id,
        status=StorageIngestJobStatus.PENDING.value,
        staging_relative_path="ws/proj/ingest/job/a.bin",
        bucket_name="bucket",
        connection_id=uuid4(),
        object_key="a.bin",
        original_filename="a.bin",
        extract_archive=False,
        archive_path_mode="preserve_folders",
    )
    claimed = {"called": False}

    class _Repo:
        def __init__(self, db):
            pass

        async def get(self, _id):
            return job

        async def try_claim_pending(self, _id):
            claimed["called"] = True
            return True

    class _Session:
        async def __aenter__(self):
            return object()

        async def __aexit__(self, exc_type, exc, tb):
            return False

        async def commit(self):
            return None

    monkeypatch.setattr(ingest_task, "get_settings", lambda: SimpleNamespace(storage_staging_upload_root="/tmp/missing"))
    monkeypatch.setattr(ingest_task, "StorageIngestJobRepository", _Repo)

    result = await ingest_task.process_storage_ingest_job({"db_factory": lambda: _Session()}, str(job_id))

    assert result == {"status": "staging_not_visible"}
    assert claimed["called"] is False
