"""Repository tests for AsyncSqlModelRepository."""

from __future__ import annotations

from typing import NoReturn, cast

import pytest
from pytest import MonkeyPatch
from sqlalchemy.exc import IntegrityError
from tests.conftest import Book, SessionFactory

from auen import derive_schemas
from auen.exceptions import ConflictError
from auen.repository import AsyncSqlModelRepository


async def test_repository_default_query_paths(get_session: SessionFactory) -> None:
    schemas = derive_schemas(Book)
    async for session in get_session():
        repo = AsyncSqlModelRepository(Book)
        created = await repo.create(session, obj_in=schemas.create(title="A", isbn="1"))
        created_book = cast(Book, created)
        assert created_book.id is not None
        assert await repo.get(session, created_book.id) is not None
        assert await repo.list(session)
        assert await repo.count(session) >= 1


async def test_repository_update_and_bulk_update_success(
    get_session: SessionFactory,
) -> None:
    schemas = derive_schemas(Book)
    async for session in get_session():
        repo = AsyncSqlModelRepository(Book)
        created = await repo.create(session, obj_in=schemas.create(title="A", isbn="1"))
        updated = await repo.update(
            session,
            db_obj=created,
            obj_in=schemas.update(title="B"),
        )
        assert cast(Book, updated).title == "B"
        bulk_updated = await repo.bulk_update(
            session,
            items=[(created, schemas.update(title="C"))],
        )
        assert cast(Book, bulk_updated[0]).title == "C"


async def test_repository_bulk_create(get_session: SessionFactory) -> None:
    from pydantic import BaseModel

    class BookCreate(BaseModel):
        title: str
        isbn: str

    get_sess_fn = get_session
    async for session in get_sess_fn():
        repo = AsyncSqlModelRepository(Book)
        result = await repo.bulk_create(
            session,
            objects_in=[BookCreate(title="A", isbn="1")],
        )
        assert len(result) == 1


async def test_repository_conflict_on_create(
    get_session: SessionFactory, monkeypatch: MonkeyPatch
) -> None:
    schemas = derive_schemas(Book)
    async for session in get_session():
        repo = AsyncSqlModelRepository(Book)

        async def _raise_commit() -> NoReturn:
            raise IntegrityError("stmt", {}, Exception("boom"))

        monkeypatch.setattr(session, "commit", _raise_commit, raising=True)
        with pytest.raises(ConflictError):
            await repo.create(session, obj_in=schemas.create(title="A", isbn="1"))


async def test_repository_conflict_on_update(
    get_session: SessionFactory, monkeypatch: MonkeyPatch
) -> None:
    schemas = derive_schemas(Book)
    async for session in get_session():
        repo = AsyncSqlModelRepository(Book)
        created = await repo.create(session, obj_in=schemas.create(title="A", isbn="1"))

        async def _raise_commit() -> NoReturn:
            raise IntegrityError("stmt", {}, Exception("boom"))

        monkeypatch.setattr(session, "commit", _raise_commit, raising=True)
        with pytest.raises(ConflictError):
            await repo.update(
                session,
                db_obj=created,
                obj_in=schemas.update(title="B"),
                partial=False,
            )


async def test_repository_conflict_on_delete(
    get_session: SessionFactory, monkeypatch: MonkeyPatch
) -> None:
    schemas = derive_schemas(Book)
    async for session in get_session():
        repo = AsyncSqlModelRepository(Book)
        created = await repo.create(session, obj_in=schemas.create(title="A", isbn="1"))

        async def _raise_commit() -> NoReturn:
            raise IntegrityError("stmt", {}, Exception("boom"))

        monkeypatch.setattr(session, "commit", _raise_commit, raising=True)
        with pytest.raises(ConflictError):
            await repo.delete(session, db_obj=created)


async def test_repository_conflict_on_bulk_create(
    get_session: SessionFactory, monkeypatch: MonkeyPatch
) -> None:
    schemas = derive_schemas(Book)
    async for session in get_session():
        repo = AsyncSqlModelRepository(Book)

        async def _raise_commit() -> NoReturn:
            raise IntegrityError("stmt", {}, Exception("boom"))

        monkeypatch.setattr(session, "commit", _raise_commit, raising=True)
        with pytest.raises(ConflictError):
            await repo.bulk_create(
                session,
                objects_in=[schemas.create(title="A", isbn="1")],
            )


async def test_repository_conflict_on_bulk_update(
    get_session: SessionFactory, monkeypatch: MonkeyPatch
) -> None:
    schemas = derive_schemas(Book)
    async for session in get_session():
        repo = AsyncSqlModelRepository(Book)
        created = await repo.create(session, obj_in=schemas.create(title="A", isbn="1"))

        async def _raise_commit() -> NoReturn:
            raise IntegrityError("stmt", {}, Exception("boom"))

        monkeypatch.setattr(session, "commit", _raise_commit, raising=True)
        with pytest.raises(ConflictError):
            await repo.bulk_update(
                session,
                items=[(created, schemas.update(title="B"))],
            )


async def test_repository_conflict_on_bulk_delete(
    get_session: SessionFactory, monkeypatch: MonkeyPatch
) -> None:
    schemas = derive_schemas(Book)
    async for session in get_session():
        repo = AsyncSqlModelRepository(Book)
        created = await repo.create(session, obj_in=schemas.create(title="A", isbn="1"))

        async def _raise_commit() -> NoReturn:
            raise IntegrityError("stmt", {}, Exception("boom"))

        monkeypatch.setattr(session, "commit", _raise_commit, raising=True)
        with pytest.raises(ConflictError):
            await repo.bulk_delete(session, db_objects=[created])
