"""Suggestion actions: accept applies the canonical tag to an image (which
feeds head training); dismiss / reject record a per-image rejection.

(The Camie allowlist bulk-apply was retired #1189 — heads + CCIP are the tag
source, and head auto-apply is the earned propagation. Accept no longer
allowlists or fans a tag out across the library.)
"""

from sqlalchemy import delete
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.ext.asyncio import AsyncSession

from ...models import TagSuggestionRejection
from ...models.tag import image_tag


class AllowlistService:
    def __init__(self, session: AsyncSession):
        self.session = session

    async def _apply_image_tag(self, image_id: int, tag_id: int, source: str):
        stmt = insert(image_tag).values(
            image_record_id=image_id, tag_id=tag_id, source=source
        ).on_conflict_do_nothing(
            index_elements=["image_record_id", "tag_id"]
        )
        await self.session.execute(stmt)

    async def _clear_rejection(self, image_id: int, tag_id: int):
        await self.session.execute(
            delete(TagSuggestionRejection)
            .where(TagSuggestionRejection.image_record_id == image_id)
            .where(TagSuggestionRejection.tag_id == tag_id)
        )

    async def accept(self, image_id: int, tag_id: int) -> None:
        """Apply the accepted tag to this image (source='ml_accepted', a head
        training positive) and clear any prior rejection."""
        await self._apply_image_tag(image_id, tag_id, source="ml_accepted")
        await self._clear_rejection(image_id, tag_id)

    async def dismiss(self, image_id: int, tag_id: int) -> None:
        stmt = insert(TagSuggestionRejection).values(
            image_record_id=image_id, tag_id=tag_id
        ).on_conflict_do_nothing(
            index_elements=["image_record_id", "tag_id"]
        )
        await self.session.execute(stmt)

    async def undismiss(self, image_id: int, tag_id: int) -> None:
        """Undo a per-image dismissal — drop the TagSuggestionRejection so the
        suggestion reverts to a live (un-rejected) state. Backs the rail's
        one-click reject-recovery (operator-asked 2026-06-27)."""
        await self._clear_rejection(image_id, tag_id)

    async def reject_applied_tag(self, image_id: int, tag_id: int) -> None:
        """Operator removed an applied tag from an image. Remove the image_tag
        row AND record a rejection so head auto-apply won't re-apply it."""
        await self.session.execute(
            image_tag.delete()
            .where(image_tag.c.image_record_id == image_id)
            .where(image_tag.c.tag_id == tag_id)
        )
        await self.dismiss(image_id, tag_id)
