import json
from pathlib import Path
from httpx import AsyncClient
import pytest

from collections.abc import AsyncGenerator
from typing import Any

from sqlalchemy.ext.asyncio import (
    AsyncSession,
    AsyncEngine,
    async_sessionmaker,
)
from advanced_alchemy.base import UUIDAuditBase
from chitai.database import models as m
from chitai import services
from chitai.database.config import config


@pytest.fixture(autouse=True)
def _patch_db(
    engine: AsyncEngine,
    sessionmaker: async_sessionmaker[AsyncSession],
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(config, "session_maker", sessionmaker)
    monkeypatch.setattr(config, "engine_instance", engine)


@pytest.fixture(autouse=True)
async def _seed_db(
    engine: AsyncEngine,
    sessionmaker: async_sessionmaker[AsyncSession],
    raw_users: list[m.User | dict[str, Any]],
    raw_libraries: list[m.Library | dict[str, Any]],
) -> AsyncGenerator[None, None]:
    """Populate test database with.

    Args:
        engine: The SQLAlchemy engine instance.
        sessionmaker: The SQLAlchemy sessionmaker factory.
        raw_users: Test users to add to the database
        raw_teams: Test teams to add to the database

    """

    metadata = UUIDAuditBase.registry.metadata
    async with engine.begin() as conn:
        await conn.run_sync(metadata.drop_all)
        await conn.run_sync(metadata.create_all)

    async with services.UserService.new(sessionmaker()) as users_service:
        await users_service.create_many(raw_users, auto_commit=True)

    async with services.LibraryService.new(sessionmaker()) as library_service:
        await library_service.create_many(raw_libraries, auto_commit=True)

    yield


@pytest.fixture
async def populated_authenticated_client(
    authenticated_client: AsyncClient, books_to_upload: list[tuple[Path, dict]]
) -> AsyncClient:
    # Upload books
    for path, data in books_to_upload:
        await upload_book_via_api(authenticated_client, data, path)

    await create_bookshelf(
        authenticated_client, {"title": "Favourites", "library_id": 1}
    )

    await add_books_to_bookshelf(authenticated_client, 1, [1, 2])

    return authenticated_client


@pytest.fixture
def books_to_upload() -> list[tuple[Path, dict]]:
    return [
        (
            Path(
                "tests/data_files/The Adventures of Sherlock Holmes - Arthur Conan Doyle.epub"
            ),
            {
                "library_id": "1",
                "title": "The Adventures of Sherlock Holmes",
                "description": "Some description...",
                "authors": "Arthur Conan Doyle",
                "publisher": "Some Publisher",
                "tags": "Mystery",
                "edition": "2",
                "language": "en",
                "pages": "300",
                "series": "Sherlock Holmes",
                "series_position": "1",
                "identifiers": json.dumps({"isbn-10": "1234567890"}),
            },
        ),
        (
            Path("tests/data_files/Frankenstein - Mary Shelley.epub"),
            {
                "library_id": "1",
                "title": "Frankenstein",
                "description": "Some description...",
                "authors": "Mary Shelley",
                "tags": "Mystery",
                "edition": "1",
                "language": "en",
                "pages": "250",
            },
        ),
        (
            Path("tests/data_files/A Tale of Two Cities - Charles Dickens.epub"),
            {
                "library_id": "1",
                "title": "A Tale of Two Cities",
                "description": "Some description...",
                "authors": "Charles Dickens",
                "tags": "Classic",
                "edition": "1",
                "language": "en",
                "pages": "500",
            },
        ),
    ]


async def upload_book_via_api(
    client: AsyncClient, book_data: dict, book_file_path: Path
) -> None:
    with book_file_path.open("rb") as file:
        files = {"files": file}

        response = await client.post("/books?library_id=1", data=book_data, files=files)

        assert response.status_code == 201


async def create_bookshelf(client: AsyncClient, shelf_data: dict) -> None:
    response = await client.post("/shelves", json=shelf_data)

    assert response.status_code == 201


async def add_books_to_bookshelf(
    client: AsyncClient, shelf_id: int, book_ids: list[int]
) -> None:
    query_params = ""
    for id in book_ids:
        query_params += f"book_ids={id}&"

    response = await client.post(
        f"/shelves/{shelf_id}/books", params={"book_ids": book_ids}
    )

    assert response.status_code == 201
