from pathlib import Path
from uuid import uuid4
import pytest

from advanced_alchemy.base import UUIDAuditBase
from litestar.testing import AsyncTestClient
from sqlalchemy import text
from sqlalchemy.ext.asyncio import (
    AsyncSession,
    AsyncEngine,
    async_sessionmaker,
    create_async_engine,
)
from sqlalchemy.engine import URL
from sqlalchemy.pool import NullPool

from chitai.database import models as m
from chitai.config import settings

from collections.abc import AsyncGenerator

from litestar import Litestar

from pytest_databases.docker.postgres import PostgresService
from sqlalchemy.orm import selectinload

pytest_plugins = [
    "tests.data_fixtures",
    "pytest_databases.docker",
    "pytest_databases.docker.postgres",
]

# Set the environment to use the testing database
# os.environ.update(
#     {
#         "POSTGRES_DB": "chitai_testing"
#     }
# )


@pytest.fixture(autouse=True)
def _patch_settings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    # PIL will not create the directory it is asked to save into, so anything that
    # imports a file carrying a cover needs it to exist first.
    covers = tmp_path / "covers"
    covers.mkdir()

    monkeypatch.setattr(settings, "book_cover_path", str(covers))
    monkeypatch.setattr(settings, "duplicate_path", str(tmp_path / "duplicates"))


@pytest.fixture(name="engine")
async def fx_engine(
    postgres_service: PostgresService,
) -> AsyncGenerator[AsyncEngine, None]:
    """PostgreSQL instance for testing"""
    engine = create_async_engine(
        URL(
            drivername="postgresql+asyncpg",
            username=postgres_service.user,
            password=postgres_service.password,
            host=postgres_service.host,
            port=postgres_service.port,
            database=postgres_service.database,
            query={},  # type:ignore[arg-type]
        ),
        echo=False,
        poolclass=NullPool,
    )

    # Add pg_trgm extension
    async with engine.begin() as conn:
        await conn.execute(text("CREATE EXTENSION IF NOT EXISTS pg_trgm"))

    # Create all tables
    metadata = UUIDAuditBase.registry.metadata
    async with engine.begin() as conn:
        await conn.run_sync(metadata.create_all)

    yield engine

    # Clean up
    async with engine.begin() as conn:
        await conn.run_sync(metadata.drop_all)
    await engine.dispose()


@pytest.fixture(name="sessionmaker")
def fx_sessionmaker(engine: AsyncEngine) -> async_sessionmaker[AsyncSession]:
    """Create sessionmaker factory."""
    return async_sessionmaker(bind=engine, expire_on_commit=False)


@pytest.fixture
async def session(
    sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[AsyncSession, None]:
    """Create database session for tests."""
    async with sessionmaker() as session:
        yield session
        await session.rollback()
        await session.close()


@pytest.fixture
async def app() -> Litestar:
    """Create Litestar app for testing."""

    from chitai.app import create_app

    return create_app()


@pytest.fixture
async def client(app: Litestar) -> AsyncGenerator[AsyncTestClient, None]:
    """Create test client."""
    async with AsyncTestClient(app=app) as client:
        yield client


@pytest.fixture
async def authenticated_client(
    client: AsyncTestClient, test_user: m.User
) -> AsyncTestClient:
    """Create authenticated test client."""

    # login and set auth headers
    login_response = await client.post(
        "access/login", data={"email": test_user.email, "password": "password123"}
    )

    assert login_response.status_code == 201

    result = login_response.json()
    token = result["access_token"]
    client.headers.update({"Authorization": f"Bearer {token}"})

    return client


@pytest.fixture
async def other_authenticated_client(
    app: Litestar, test_user2: m.User
) -> AsyncGenerator[AsyncTestClient, None]:
    """Create second authenticated test client as different user."""
    async with AsyncTestClient(app=app) as other_client:
        login_response = await other_client.post(
            "access/login", data={"email": test_user2.email, "password": "password234"}
        )
        assert login_response.status_code == 201
        result = login_response.json()
        token = result["access_token"]
        other_client.headers.update({"Authorization": f"Bearer {token}"})
        yield other_client


# Service fixtures
from chitai import services


@pytest.fixture
async def user_service(
    sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.UserService, None]:
    """Create UserService instance."""
    async with sessionmaker() as session:
        async with services.UserService.new(session) as service:
            yield service


@pytest.fixture
async def library_service(
    sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.LibraryService, None]:
    """Create LibraryService instance."""
    async with sessionmaker() as session:
        async with services.LibraryService.new(session) as service:
            yield service


@pytest.fixture
async def books_service(
    sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.BookService, None]:
    """Create BookService instance."""
    async with sessionmaker() as session:
        async with services.BookService.new(
            session,
            load=[
                selectinload(m.Book.author_links).selectinload(m.BookAuthorLink.author),
                selectinload(m.Book.tag_links).selectinload(m.BookTagLink.tag),
                m.Book.publisher,
                m.Book.files,
                m.Book.identifiers,
                m.Book.series,
            ],
        ) as service:
            yield service


@pytest.fixture
async def bookshelf_service(
    sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.ShelfService]:
    """Create ShelfService instance."""
    async with sessionmaker() as session:
        async with services.ShelfService.new(session) as service:
            yield service


@pytest.fixture
async def kosync_progress_service(
    sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.KosyncProgressService, None]:
    """Create KosyncProgressService instance."""
    async with sessionmaker() as session:
        async with services.KosyncProgressService.new(session) as service:
            yield service


@pytest.fixture
async def kosync_device_service(
    sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.KosyncDeviceService, None]:
    """Create KosyncDeviceService instance."""
    async with sessionmaker() as session:
        async with services.KosyncDeviceService.new(session) as service:
            yield service


# Data fixtures


@pytest.fixture
async def test_user(session: AsyncSession) -> AsyncGenerator[m.User, None]:
    """Create a test user."""

    unique_id = str(uuid4())[:8]
    user = m.User(
        email=f"user{unique_id}@example.com",
        password="password123",
    )

    session.add(user)
    await session.commit()
    await session.refresh(user)
    yield user


@pytest.fixture
async def test_user2(session: AsyncSession) -> AsyncGenerator[m.User, None]:
    """Create another test user."""

    unique_id = str(uuid4())[:8]
    user = m.User(
        email=f"user{unique_id}@example.com",
        password="password234",
    )

    session.add(user)
    await session.commit()
    await session.refresh(user)
    yield user


@pytest.fixture
async def test_library(
    session: AsyncSession, tmp_path: Path
) -> AsyncGenerator[m.Library, None]:
    """Create a test library."""

    library = m.Library(
        name="Testing Library",
        slug="testing-library",
        root_path=str(tmp_path),
        path_template="{author_name}/{title}.{ext}",
        read_only=False,
    )

    session.add(library)
    await session.commit()
    await session.refresh(library)
    yield library
