"""ArtistVisit unseen-count badge + ArtistService.overview banner data.

Covers:
- Directory cards include `unseen_count`
- LEFT JOIN keeps artists without a visit row (treats NULL as "never
  visited" → all images unseen)
- overview() returns `unseen_count_at_visit` and stamps the visit
- find_or_create autoseeds a visit row so freshly imported content
  doesn't show up as unseen
- Repeat overview() returns 0 (since the previous visit just stamped
  last_viewed_at = NOW())
"""

from datetime import UTC, datetime, timedelta

import pytest
from sqlalchemy import select

from backend.app.models import Artist, ArtistVisit, ImageRecord
from backend.app.services.artist_directory_service import ArtistDirectoryService
from backend.app.services.artist_service import ArtistService

pytestmark = pytest.mark.integration


_LONG_AGO = datetime(2000, 1, 1, tzinfo=UTC)
_RECENTLY = datetime(2099, 1, 1, tzinfo=UTC)


async def _seed_artist(db, name: str) -> Artist:
    a = Artist(name=name, slug=name.lower().replace(" ", "-"))
    db.add(a)
    await db.flush()
    return a


async def _seed_visit(db, artist_id: int, when: datetime) -> None:
    db.add(ArtistVisit(artist_id=artist_id, last_viewed_at=when))
    await db.flush()


async def _seed_image(db, artist_id: int, suffix: str, *, created_at: datetime) -> None:
    db.add(ImageRecord(
        path=f"/images/visit-{suffix}.jpg",
        sha256=f"visit{suffix}".ljust(64, "0")[:64],
        size_bytes=10, mime="image/jpeg", width=10, height=10,
        origin="downloaded", artist_id=artist_id,
        created_at=created_at,
    ))
    await db.flush()


# --- Directory unseen_count -----------------------------------------------


@pytest.mark.asyncio
async def test_directory_unseen_count_zero_when_no_images(db):
    await _seed_artist(db, "zoey-empty-visit")
    await db.commit()

    page = await ArtistDirectoryService(db).list_artists(
        q="zoey-empty-visit", platform=None, cursor=None, limit=60,
    )
    target = next(c for c in page.cards if c["name"] == "zoey-empty-visit")
    assert target["unseen_count"] == 0


@pytest.mark.asyncio
async def test_directory_unseen_counts_only_images_after_visit(db):
    a = await _seed_artist(db, "alice-visit-mix")
    await _seed_visit(db, a.id, _LONG_AGO + timedelta(days=365))
    # Two images BEFORE the visit (seen), three AFTER (unseen).
    for i in range(2):
        await _seed_image(db, a.id, f"old-{i}", created_at=_LONG_AGO)
    for i in range(3):
        await _seed_image(db, a.id, f"new-{i}", created_at=_RECENTLY)
    await db.commit()

    page = await ArtistDirectoryService(db).list_artists(
        q="alice-visit-mix", platform=None, cursor=None, limit=60,
    )
    target = next(c for c in page.cards if c["name"] == "alice-visit-mix")
    assert target["image_count"] == 5
    assert target["unseen_count"] == 3


@pytest.mark.asyncio
async def test_directory_treats_missing_visit_as_never_visited(db):
    """No ArtistVisit row → defensive count of all images as unseen.

    Shouldn't happen in practice (migration 0034 seeds existing
    artists, find_or_create autoseeds new ones), but the directory
    query must not regress to "0 unseen" if a row is missing.
    """
    a = await _seed_artist(db, "bob-no-visit")
    for i in range(4):
        await _seed_image(db, a.id, f"orphan-{i}", created_at=_RECENTLY)
    await db.commit()

    page = await ArtistDirectoryService(db).list_artists(
        q="bob-no-visit", platform=None, cursor=None, limit=60,
    )
    target = next(c for c in page.cards if c["name"] == "bob-no-visit")
    assert target["unseen_count"] == 4


# --- overview() marks visit + returns count -------------------------------


@pytest.mark.asyncio
async def test_overview_returns_unseen_count_at_visit_and_stamps_now(db):
    a = await _seed_artist(db, "carol-stamp")
    await _seed_visit(db, a.id, _LONG_AGO)
    await _seed_image(db, a.id, "stamp-1", created_at=_RECENTLY)
    await _seed_image(db, a.id, "stamp-2", created_at=_RECENTLY)
    await db.commit()

    data = await ArtistService(db).overview("carol-stamp")
    assert data is not None
    assert data["unseen_count_at_visit"] == 2

    # last_viewed_at advanced to NOW() — directly check the row.
    visit_at = (await db.execute(
        select(ArtistVisit.last_viewed_at).where(ArtistVisit.artist_id == a.id)
    )).scalar_one()
    assert visit_at > _LONG_AGO


@pytest.mark.asyncio
async def test_overview_repeat_call_returns_zero(db):
    a = await _seed_artist(db, "dana-repeat")
    await _seed_visit(db, a.id, _LONG_AGO)
    await _seed_image(db, a.id, "repeat-1", created_at=_LONG_AGO + timedelta(days=1))
    await db.commit()

    first = await ArtistService(db).overview("dana-repeat")
    assert first is not None
    assert first["unseen_count_at_visit"] == 1

    # Second call: no new images, visit just stamped → count is 0.
    second = await ArtistService(db).overview("dana-repeat")
    assert second is not None
    assert second["unseen_count_at_visit"] == 0


# --- find_or_create autoseeds the visit row ------------------------------


@pytest.mark.asyncio
async def test_find_or_create_autoseeds_visit_row(db):
    artist, created = await ArtistService(db).find_or_create("Eve-Autoseed")
    assert created is True

    row = (await db.execute(
        select(ArtistVisit.last_viewed_at).where(ArtistVisit.artist_id == artist.id)
    )).scalar_one_or_none()
    assert row is not None


@pytest.mark.asyncio
async def test_find_or_create_existing_does_not_reset_visit(db):
    """Calling find_or_create on an existing artist returns it as-is —
    must NOT touch the visit row's timestamp."""
    a = await _seed_artist(db, "Frank-Existing")
    await _seed_visit(db, a.id, _LONG_AGO)
    await db.commit()

    artist, created = await ArtistService(db).find_or_create("Frank-Existing")
    assert created is False
    assert artist.id == a.id

    visit_at = (await db.execute(
        select(ArtistVisit.last_viewed_at).where(ArtistVisit.artist_id == a.id)
    )).scalar_one()
    assert visit_at == _LONG_AGO
