"""Comments + attachments — the conversation (M4), which the rally timeline
(plan §3.5) interleaves with touches into one thread: the thread IS the
play-by-play.

A comment's parent is polymorphic (parent_type, parent_id) per the review
app's learnings (§1: NOT a nullable-FK-per-parent-type). Only 'ball' exists
today; the allowed-values check lives in services.conversation, not a DB
CHECK/enum, so a future parent kind is data, not a migration.

acting_node_id mirrors touches.acting_node_id exactly: the node context a
comment was WRITTEN in — its visibility scope for the read-time projection
that lands at M6 (plan §4.3: "comments are scoped, not just anonymized").
Recorded from day one (this migration) so no history is lost waiting for
that projection; nothing filters on it yet (M4 reads are flat, same as the
M1 board and the M2 rally).
"""
import datetime

from sqlalchemy import ForeignKey, Index, Integer, String, Text, TIMESTAMP, text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column

from .base import Base, created_at, uuid_pk


class Comment(Base):
    __tablename__ = "comments"
    __table_args__ = (
        Index(
            "ix_comments_parent_type_parent_id_created_at",
            "parent_type",
            "parent_id",
            "created_at",
        ),
    )

    id: Mapped[str] = uuid_pk()
    # Polymorphic parent: 'ball' only for now (services.conversation enforces
    # the allowed-values list). Deliberately NOT a ForeignKey — a future
    # parent_type points at a different table entirely.
    parent_type: Mapped[str] = mapped_column(String, nullable=False)
    parent_id: Mapped[str] = mapped_column(UUID(as_uuid=True), nullable=False)
    author_id: Mapped[str | None] = mapped_column(
        UUID(as_uuid=True), ForeignKey("players.id", ondelete="SET NULL")
    )
    acting_node_id: Mapped[str | None] = mapped_column(
        UUID(as_uuid=True), ForeignKey("team_nodes.id", ondelete="SET NULL")
    )
    body: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("''"))
    # AUTHOR-EDITABLE later via PATCH (backdating, review-learnings §9) —
    # server default now() at creation, explicit override wins on insert.
    created_at: Mapped[datetime.datetime] = created_at()
    # NULL until the body is first edited (services.conversation stamps it;
    # a created_at-only backdate deliberately leaves this untouched — that's
    # not "this text changed").
    edited_at: Mapped[datetime.datetime | None] = mapped_column(TIMESTAMP(timezone=True))


class Attachment(Base):
    __tablename__ = "attachments"
    __table_args__ = (Index("ix_attachments_comment_id", "comment_id"),)

    id: Mapped[str] = uuid_pk()
    comment_id: Mapped[str] = mapped_column(
        UUID(as_uuid=True), ForeignKey("comments.id", ondelete="CASCADE"), nullable=False
    )
    # Bare filename under settings.upload_dir, UUID-named at upload time
    # (services.attachments.store_upload) — never a client-chosen name or a
    # path with separators. services.attachments.validate_stored_filename is
    # the one place that's enforced, read side and write side alike.
    file_path: Mapped[str] = mapped_column(String, nullable=False)
    mime: Mapped[str] = mapped_column(String, nullable=False)
    bytes: Mapped[int] = mapped_column(Integer, nullable=False)
    created_at: Mapped[datetime.datetime] = created_at()
