"""PostAttachment — a non-art file preserved from a post.

Art images become ImageRecords; everything else a post contained
(archives, .exe, .pdf, ...) is captured here so nothing is lost.
post_id is nullable (set only when an adjacent sidecar yields a Post);
artist_id mirrors the canonical attribution model (FC-2d-vii-c). Both
FKs are SET NULL so deleting a Post/Artist never deletes the preserved
binary row.
"""

from datetime import datetime

from sqlalchemy import (
    BigInteger,
    DateTime,
    ForeignKey,
    Index,
    Integer,
    String,
    Text,
    func,
    text,
)
from sqlalchemy.orm import Mapped, mapped_column

from .base import Base


class PostAttachment(Base):
    __tablename__ = "post_attachment"
    # Dedup is PER-POST, not global (2026-06-08): the same non-art file attached
    # to many posts gets one row per post over a single sha-addressed blob, so no
    # post is left a bare shell. Partial uniques: (post_id, sha256) for real posts;
    # (sha256) alone for the NULL-post filesystem case (one row per file there).
    __table_args__ = (
        Index(
            "uq_post_attachment_post_sha",
            "post_id", "sha256",
            unique=True,
            postgresql_where=text("post_id IS NOT NULL"),
        ),
        Index(
            "uq_post_attachment_null_post_sha",
            "sha256",
            unique=True,
            postgresql_where=text("post_id IS NULL"),
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    post_id: Mapped[int | None] = mapped_column(
        ForeignKey("post.id", ondelete="SET NULL"), nullable=True, index=True
    )
    artist_id: Mapped[int | None] = mapped_column(
        ForeignKey("artist.id", ondelete="SET NULL"), nullable=True, index=True
    )
    sha256: Mapped[str] = mapped_column(
        String(64), nullable=False, index=True
    )
    path: Mapped[str] = mapped_column(Text, nullable=False)
    original_filename: Mapped[str] = mapped_column(Text, nullable=False)
    ext: Mapped[str] = mapped_column(String(32), nullable=False)
    mime: Mapped[str | None] = mapped_column(String(128), nullable=True)
    size_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False)
    captured_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )


def attachment_download_url(attachment_id: int) -> str:
    """The path that streams this attachment's bytes.

    Both serializers that expose an attachment to the frontend
    (`provenance_service`, `post_feed_service`) built this literal themselves,
    so changing the route in `api/attachments.py` meant two edits and only one
    would be remembered (#3072). `test_attachment_download_url` pins it against
    the app's registered rule, so the drift is caught rather than trusted to.
    """
    return f"/api/attachments/{attachment_id}/download"
