import pytest
from sqlalchemy import select

from backend.app.models import TagAllowlist, TagKind, TagSuggestionRejection
from backend.app.models.tag import image_tag
from backend.app.services.ml.allowlist import AllowlistService
from backend.app.services.tag_service import TagService

pytestmark = pytest.mark.integration


async def _make_image(db):
    from backend.app.models import ImageRecord
    img = ImageRecord(
        path="/images/x.jpg", sha256="x" * 64, size_bytes=1,
        mime="image/jpeg", width=1, height=1,
        origin="imported_filesystem", integrity_status="unknown",
    )
    db.add(img)
    await db.flush()
    return img


@pytest.mark.asyncio
async def test_accept_applies_and_allowlists(db):
    img = await _make_image(db)
    tag = await TagService(db).find_or_create("Hero", TagKind.character)
    svc = AllowlistService(db)
    newly_added = await svc.accept(img.id, tag.id)
    assert newly_added is True

    applied = (
        await db.execute(
            select(image_tag.c.source)
            .where(image_tag.c.image_record_id == img.id)
            .where(image_tag.c.tag_id == tag.id)
        )
    ).scalar_one()
    assert applied == "ml_accepted"
    assert await db.get(TagAllowlist, tag.id) is not None


@pytest.mark.asyncio
async def test_accept_idempotent_allowlist(db):
    img = await _make_image(db)
    tag = await TagService(db).find_or_create("Hero2", TagKind.character)
    svc = AllowlistService(db)
    assert await svc.accept(img.id, tag.id) is True
    assert await svc.accept(img.id, tag.id) is False


@pytest.mark.asyncio
async def test_reject_applied_tag_records_rejection(db):
    img = await _make_image(db)
    tag = await TagService(db).find_or_create("Removeme", TagKind.general)
    svc = AllowlistService(db)
    await svc.accept(img.id, tag.id)
    await svc.reject_applied_tag(img.id, tag.id)

    still_applied = (
        await db.execute(
            select(image_tag.c.tag_id)
            .where(image_tag.c.image_record_id == img.id)
            .where(image_tag.c.tag_id == tag.id)
        )
    ).scalar_one_or_none()
    assert still_applied is None
    rej = await db.get(TagSuggestionRejection, (img.id, tag.id))
    assert rej is not None


@pytest.mark.asyncio
async def test_dismiss_records_rejection(db):
    img = await _make_image(db)
    tag = await TagService(db).find_or_create("Dismissme", TagKind.general)
    await AllowlistService(db).dismiss(img.id, tag.id)
    assert await db.get(TagSuggestionRejection, (img.id, tag.id)) is not None


@pytest.mark.asyncio
async def test_add_alias_and_accept(db):
    img = await _make_image(db)
    canonical = await TagService(db).find_or_create(
        "Canonical Char", TagKind.character
    )
    svc = AllowlistService(db)
    await svc.add_alias_and_accept(
        img.id, "model_char_name", "character", canonical.id
    )
    from backend.app.services.ml.aliases import AliasService
    resolved = await AliasService(db).resolve("model_char_name", "character")
    assert resolved.id == canonical.id
    assert await db.get(TagAllowlist, canonical.id) is not None


@pytest.mark.asyncio
async def test_update_threshold_and_remove(db):
    tag = await TagService(db).find_or_create("Thr", TagKind.general)
    svc = AllowlistService(db)
    img = await _make_image(db)
    await svc.accept(img.id, tag.id)
    await svc.update_threshold(tag.id, 0.80)
    row = await db.get(TagAllowlist, tag.id)
    assert abs(row.min_confidence - 0.80) < 1e-6
    await svc.remove(tag.id)
    assert await db.get(TagAllowlist, tag.id) is None


@pytest.mark.asyncio
async def test_update_threshold_clamped_to_store_floor(db):
    # A min_confidence below the store floor (default 0.70) is clamped up —
    # predictions below the floor aren't stored, so a lower threshold can't
    # apply more permissively than the floor (#764).
    tag = await TagService(db).find_or_create("Lowthr", TagKind.general)
    svc = AllowlistService(db)
    img = await _make_image(db)
    await svc.accept(img.id, tag.id)
    await svc.update_threshold(tag.id, 0.30)
    row = await db.get(TagAllowlist, tag.id)
    assert abs(row.min_confidence - 0.70) < 1e-6
