"""Integration tests for the lazy-materialization foundation:
``TrackRepository.materialize`` and ``Album``/``ArtistRepository.get_or_create_remote``.

Requires a reachable Postgres; skips otherwise (same pattern as
``test_upload_stream_api.py``).
"""

import asyncio
import uuid
from collections.abc import AsyncIterator

import pytest
from app.infrastructure.db import Base, dispose_engine, get_engine, session_scope
from app.infrastructure.db.models.enums import TrackAvailability
from app.infrastructure.db.repositories import (
    SqlAlchemyAlbumRepository,
    SqlAlchemyArtistRepository,
    SqlAlchemyTrackRepository,
)

pytestmark = pytest.mark.asyncio

_db_reachable_cache: bool | None = None


async def _db_reachable() -> bool:
    global _db_reachable_cache
    if _db_reachable_cache is not None:
        return _db_reachable_cache

    from sqlalchemy import text

    try:
        async with asyncio.timeout(3):
            async with get_engine().connect() as conn:
                await conn.execute(text("SELECT 1"))
        _db_reachable_cache = True
    except Exception:
        _db_reachable_cache = False
    return _db_reachable_cache


@pytest.fixture
async def db() -> AsyncIterator[None]:
    if not await _db_reachable():
        pytest.skip("Postgres not reachable — integration test skipped.")

    async with get_engine().begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)

    yield None

    async with get_engine().begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
    await dispose_engine()


async def test_placeholder_track_materializes_in_place(db: None) -> None:
    """A remote placeholder (no storage) gets its audio fields filled in by
    ``materialize`` without changing ``track.id`` — the stable content id that
    likes/playlists/queue may already reference."""
    async with session_scope() as session:
        artists = SqlAlchemyArtistRepository(session)
        tracks = SqlAlchemyTrackRepository(session)
        artist = await artists.get_or_create("Some Artist")

        track_id = uuid.uuid4()
        placeholder = await tracks.add(
            id=track_id,
            title="Remote Track",
            artist_id=artist.id,
            storage_uri=None,
            file_format=None,
            file_size=None,
            source="youtube",
            source_id="abc123",
            metadata_status="pending",
            added_by=None,
            availability=TrackAvailability.REMOTE.value,
        )
        assert placeholder.availability == "remote"
        assert placeholder.storage_uri is None

        materialized = await tracks.materialize(
            track_id,
            storage_uri="tracks/ab/abc123.m4a",
            file_format="m4a",
            file_size=12345,
            bitrate=160,
        )

        assert materialized.id == track_id
        assert materialized.availability == "local"
        assert materialized.storage_uri == "tracks/ab/abc123.m4a"
        assert materialized.file_format == "m4a"
        assert materialized.file_size == 12345


async def test_artist_get_or_create_remote_dedups_by_remote_id(db: None) -> None:
    async with session_scope() as session:
        artists = SqlAlchemyArtistRepository(session)

        first = await artists.get_or_create_remote(
            name="Daft Punk", source="youtube", source_id="UCabc"
        )
        again = await artists.get_or_create_remote(
            name="Daft Punk (different display name)", source="youtube", source_id="UCabc"
        )

        assert first.id == again.id
        assert again.source == "youtube"
        assert again.source_id == "UCabc"


async def test_artist_get_or_create_remote_binds_existing_local_artist(db: None) -> None:
    async with session_scope() as session:
        artists = SqlAlchemyArtistRepository(session)

        local = await artists.get_or_create("Pink Floyd")
        remote = await artists.get_or_create_remote(
            name="Pink Floyd", source="youtube", source_id="UCxyz"
        )

        assert remote.id == local.id
        assert remote.source == "youtube"
        assert remote.source_id == "UCxyz"


async def test_album_get_or_create_remote_dedups_by_remote_id(db: None) -> None:
    async with session_scope() as session:
        artists = SqlAlchemyArtistRepository(session)
        albums = SqlAlchemyAlbumRepository(session)
        artist = await artists.get_or_create("Daft Punk")

        first = await albums.get_or_create_remote(
            title="Discovery",
            artist_id=artist.id,
            year=2001,
            musicbrainz_id=None,
            source="youtube",
            source_id="MPREb_abc",
        )
        again = await albums.get_or_create_remote(
            title="Discovery",
            artist_id=artist.id,
            year=None,
            musicbrainz_id=None,
            source="youtube",
            source_id="MPREb_abc",
        )

        assert first.id == again.id
        assert again.source == "youtube"
        assert again.source_id == "MPREb_abc"
        assert again.year == 2001
