"""Shared ML helpers extracted in the DRY pass (milestone #161). These pin the
single sources the auto-apply sweeps now trust, so a future edit can't silently
drift them: `_applied_or_rejected` is the skip-set used by auto_apply_sweep,
system_tag_auto_apply_sweep (heads.py) and scheduled_ccip_auto_apply (tasks/ml.py);
`_sigmoid` is the head score→prob transform used at every scoring site."""
import pytest

from backend.app.models import ImageRecord, Tag, TagKind, TagSuggestionRejection
from backend.app.models.tag import image_tag
from backend.app.services.ml.training_data import _applied_or_rejected


def test_sigmoid_matches_naive_form():
    import numpy as np

    from backend.app.services.ml.heads import _sigmoid

    z = np.array([-3.0, -0.5, 0.0, 1.5, 12.0], dtype=np.float32)
    assert np.allclose(_sigmoid(z, np), 1.0 / (1.0 + np.exp(-z)))
    assert float(_sigmoid(np.array([0.0]), np)[0]) == pytest.approx(0.5)


@pytest.mark.integration
def test_applied_or_rejected_unions_applied_any_source_and_rejected(db_sync):
    a = Tag(name="dry-helper-a", kind=TagKind.general)
    b = Tag(name="dry-helper-b", kind=TagKind.general)
    db_sync.add_all([a, b])
    db_sync.flush()

    imgs = []
    for i in range(5):
        img = ImageRecord(
            path=f"/images/dryhelp{i}.jpg", sha256=f"{i:064d}", size_bytes=1,
            mime="image/jpeg", width=1, height=1, origin="imported_filesystem",
            integrity_status="unknown", siglip_embedding=[0.0] * 1152,
        )
        db_sync.add(img)
        imgs.append(img)
    db_sync.flush()

    # tag a: applied manually (img0), applied by an AUTO source (img1), rejected (img2).
    db_sync.execute(image_tag.insert().values(
        image_record_id=imgs[0].id, tag_id=a.id, source="manual"))
    db_sync.execute(image_tag.insert().values(
        image_record_id=imgs[1].id, tag_id=a.id, source="head_auto"))
    db_sync.add(TagSuggestionRejection(image_record_id=imgs[2].id, tag_id=a.id))
    # tag b: applied to img3 only.
    db_sync.execute(image_tag.insert().values(
        image_record_id=imgs[3].id, tag_id=b.id, source="manual"))
    db_sync.flush()

    skip = _applied_or_rejected(db_sync, [a.id, b.id])

    # Applied-under-ANY-source (manual + head_auto) ∪ rejected, kept per-tag; the
    # untouched image (img4) appears under neither tag.
    assert skip[a.id] == {imgs[0].id, imgs[1].id, imgs[2].id}
    assert skip[b.id] == {imgs[3].id}
    assert imgs[4].id not in skip[a.id]
    assert imgs[4].id not in skip[b.id]
