import pytest
from sqlalchemy import func, select

from backend.app.models import ImageRecord, TagKind
from backend.app.models.tag import image_tag
from backend.app.models.tag_suggestion_rejection import TagSuggestionRejection
from backend.app.services.bulk_tag_service import BulkTagService
from backend.app.services.tag_service import TagService

pytestmark = pytest.mark.integration

_SEQ = 0


async def _img(db):
    global _SEQ
    _SEQ += 1
    rec = ImageRecord(
        path=f"/tmp/fc_bulk_{_SEQ}.png",
        sha256=f"{_SEQ:064d}",
        size_bytes=1,
        mime="image/png",
        origin="uploaded",
    )
    db.add(rec)
    await db.flush()
    return rec.id


@pytest.mark.asyncio
async def test_common_tags_only_returns_tags_on_every_image(db):
    tags = TagService(db)
    t_all = await tags.find_or_create("OnAll", TagKind.general)
    t_some = await tags.find_or_create("OnSome", TagKind.general)
    i1, i2 = await _img(db), await _img(db)
    await tags.add_to_image(i1, t_all.id)
    await tags.add_to_image(i2, t_all.id)
    await tags.add_to_image(i1, t_some.id)  # only one image
    svc = BulkTagService(db)
    result = await svc.common_tags([i1, i2])
    assert [t["id"] for t in result] == [t_all.id]
    assert result[0]["name"] == "OnAll"
    assert result[0]["kind"] == "general"


@pytest.mark.asyncio
async def test_common_tags_empty_when_no_images(db):
    svc = BulkTagService(db)
    assert await svc.common_tags([]) == []


@pytest.mark.asyncio
async def test_bulk_add_applies_and_counts_idempotent(db):
    tags = TagService(db)
    tag = await tags.find_or_create("BulkAdd", TagKind.general)
    i1, i2 = await _img(db), await _img(db)
    svc = BulkTagService(db)
    added = await svc.bulk_add([i1, i2], tag.id)
    assert added == 2
    # Idempotent: re-adding adds nothing.
    again = await svc.bulk_add([i1, i2], tag.id)
    assert again == 0
    rows = (
        await db.execute(
            select(image_tag.c.image_record_id, image_tag.c.source).where(
                image_tag.c.tag_id == tag.id
            )
        )
    ).all()
    assert {r.image_record_id for r in rows} == {i1, i2}
    assert all(r.source == "manual" for r in rows)


@pytest.mark.asyncio
async def test_bulk_add_records_explicit_source(db):
    tags = TagService(db)
    tag = await tags.find_or_create("BulkAddML", TagKind.general)
    i1 = await _img(db)
    svc = BulkTagService(db)
    await svc.bulk_add([i1], tag.id, source="ml_accepted")
    src = await db.scalar(
        select(image_tag.c.source).where(image_tag.c.tag_id == tag.id)
    )
    assert src == "ml_accepted"


@pytest.mark.asyncio
async def test_bulk_remove_counts_and_records_rejections(db):
    tags = TagService(db)
    tag = await tags.find_or_create("BulkRem", TagKind.general)
    i1, i2 = await _img(db), await _img(db)
    svc = BulkTagService(db)
    await svc.bulk_add([i1, i2], tag.id)
    removed = await svc.bulk_remove([i1, i2], tag.id)
    assert removed == 2
    remaining = await db.scalar(
        select(func.count())
        .select_from(image_tag)
        .where(image_tag.c.tag_id == tag.id)
    )
    assert remaining == 0
    rej = (
        await db.execute(
            select(TagSuggestionRejection.image_record_id).where(
                TagSuggestionRejection.tag_id == tag.id
            )
        )
    ).scalars().all()
    assert set(rej) == {i1, i2}


@pytest.mark.asyncio
async def test_bulk_remove_rejection_is_idempotent(db):
    tags = TagService(db)
    tag = await tags.find_or_create("BulkRem2", TagKind.general)
    i1 = await _img(db)
    svc = BulkTagService(db)
    await svc.bulk_add([i1], tag.id)
    await svc.bulk_remove([i1], tag.id)
    # Second remove: nothing to delete, rejection already present, no error.
    removed = await svc.bulk_remove([i1], tag.id)
    assert removed == 0
