from datetime import UTC, datetime, timedelta

import pytest

from backend.app.models import (
    Artist,
    ImageProvenance,
    ImageRecord,
    Post,
    Source,
)
from backend.app.services.gallery_service import GalleryService

pytestmark = pytest.mark.integration


async def _img(db, n):
    base = datetime.now(UTC)
    rec = ImageRecord(
        path=f"/images/f/{n}.jpg", sha256=f"f{n:063d}",
        size_bytes=1, mime="image/jpeg", width=1, height=1,
        origin="imported_filesystem", integrity_status="unknown",
    )
    rec.created_at = base - timedelta(minutes=n)
    rec.effective_date = rec.created_at  # no primary post → tracks created_at (0035)
    db.add(rec)
    await db.flush()
    return rec


async def _post(db, artist_name, slug, ext):
    a = Artist(name=artist_name, slug=slug)
    db.add(a)
    await db.flush()
    s = Source(artist_id=a.id, platform="patreon",
               url=f"https://patreon.test/{slug}")
    db.add(s)
    await db.flush()
    p = Post(source_id=s.id, artist_id=a.id, external_post_id=ext)
    db.add(p)
    await db.flush()
    return a, s, p


@pytest.mark.asyncio
async def test_scroll_post_id_filter(db):
    i1 = await _img(db, 1)
    i2 = await _img(db, 2)
    await _img(db, 3)  # unmatched image — proves the filter excludes it
    _, s, p = await _post(db, "A", "a", "10")
    db.add(ImageProvenance(image_record_id=i1.id, post_id=p.id,
                           source_id=s.id))
    db.add(ImageProvenance(image_record_id=i2.id, post_id=p.id,
                           source_id=s.id))
    await db.flush()
    svc = GalleryService(db)
    page = await svc.scroll(cursor=None, limit=10, post_id=p.id)
    assert {x.id for x in page.images} == {i1.id, i2.id}


# test_scroll_post_id_dedups_multi_rows removed 2026-05-26: it deliberately
# inserted two ImageProvenance rows with the same (image_record_id, post_id),
# now prevented at the DB layer by uq_image_provenance_image_post (alembic
# 0021). The EXISTS-based dedup in _provenance_clause is still useful for the
# artist-id filter (one image legitimately joins many provenance rows via
# different posts under the same artist), so the gallery_service logic is
# unchanged.


@pytest.mark.asyncio
async def test_scroll_artist_id_filter(db):
    i1 = await _img(db, 1)
    await _img(db, 2)  # unmatched image — proves the filter excludes it
    a, s, p = await _post(db, "A", "a", "10")
    db.add(ImageProvenance(image_record_id=i1.id, post_id=p.id,
                           source_id=s.id))
    await db.flush()
    svc = GalleryService(db)
    page = await svc.scroll(cursor=None, limit=10, artist_id=a.id)
    assert [x.id for x in page.images] == [i1.id]


@pytest.mark.asyncio
async def test_post_id_excludes_other_filters(db):
    svc = GalleryService(db)
    with pytest.raises(ValueError):
        await svc.scroll(cursor=None, limit=10, tag_ids=[1], post_id=2)


@pytest.mark.asyncio
async def test_timeline_and_jump_accept_post_id(db):
    i1 = await _img(db, 1)
    _, s, p = await _post(db, "A", "a", "10")
    db.add(ImageProvenance(image_record_id=i1.id, post_id=p.id,
                           source_id=s.id))
    await db.flush()
    svc = GalleryService(db)
    buckets = await svc.timeline(post_id=p.id)
    assert sum(b.count for b in buckets) == 1
    cur = await svc.jump_cursor(
        year=i1.created_at.year, month=i1.created_at.month, post_id=p.id
    )
    assert cur is not None


@pytest.mark.asyncio
async def test_api_scroll_rejects_combined_filters(client):
    resp = await client.get("/api/gallery/scroll?tag_id=1&post_id=2")
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_api_timeline_rejects_combined_filters(client):
    resp = await client.get("/api/gallery/timeline?post_id=1&artist_id=2")
    assert resp.status_code == 400
