import datetime

from sqlalchemy import Boolean, Column, Date, DateTime, ForeignKey, Index, Integer, Table, Text
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship

from fabledassistant.models import Base


# Junction tables. People, Places, Tasks, and (regular) Notes all live in
# the `notes` table — the four junctions are kept separate (rather than one
# merged table with a discriminator) so per-kind queries don't require a
# filter, and so the schema is explicit about which links are which.
moment_people = Table(
    "moment_people",
    Base.metadata,
    Column("moment_id", Integer, ForeignKey("moments.id", ondelete="CASCADE"), primary_key=True),
    Column("person_id", Integer, ForeignKey("notes.id", ondelete="CASCADE"), primary_key=True),
)

moment_places = Table(
    "moment_places",
    Base.metadata,
    Column("moment_id", Integer, ForeignKey("moments.id", ondelete="CASCADE"), primary_key=True),
    Column("place_id", Integer, ForeignKey("notes.id", ondelete="CASCADE"), primary_key=True),
)

moment_tasks = Table(
    "moment_tasks",
    Base.metadata,
    Column("moment_id", Integer, ForeignKey("moments.id", ondelete="CASCADE"), primary_key=True),
    Column("task_id", Integer, ForeignKey("notes.id", ondelete="CASCADE"), primary_key=True),
)

moment_notes = Table(
    "moment_notes",
    Base.metadata,
    Column("moment_id", Integer, ForeignKey("moments.id", ondelete="CASCADE"), primary_key=True),
    Column("note_id", Integer, ForeignKey("notes.id", ondelete="CASCADE"), primary_key=True),
)


class Moment(Base):
    """A small structured extraction from a journal conversation.

    Many per day. Emitted by the LLM via the `record_moment` tool when it
    notices a meaningful beat. Stored separately from Notes — they are
    different in kind: Notes are curated artifacts; Moments are ambient
    trace data with their own embedding index, so notes-RAG and journal-RAG
    cannot cross-contaminate.
    """

    __tablename__ = "moments"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
    )
    conversation_id: Mapped[int | None] = mapped_column(
        Integer, ForeignKey("conversations.id", ondelete="SET NULL"), nullable=True
    )
    source_message_id: Mapped[int | None] = mapped_column(
        Integer, ForeignKey("messages.id", ondelete="SET NULL"), nullable=True
    )
    day_date: Mapped[datetime.date] = mapped_column(Date, nullable=False)
    occurred_at: Mapped[datetime.datetime] = mapped_column(
        DateTime(timezone=True), nullable=False
    )
    recorded_at: Mapped[datetime.datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        default=lambda: datetime.datetime.now(datetime.timezone.utc),
    )
    content: Mapped[str] = mapped_column(Text, nullable=False)
    raw_excerpt: Mapped[str | None] = mapped_column(Text, nullable=True)
    tags: Mapped[list[str]] = mapped_column(ARRAY(Text), nullable=False, default=list)
    pinned: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)

    people = relationship("Note", secondary=moment_people, lazy="selectin", viewonly=True)
    places = relationship("Note", secondary=moment_places, lazy="selectin", viewonly=True)
    tasks = relationship("Note", secondary=moment_tasks, lazy="selectin", viewonly=True)
    notes = relationship("Note", secondary=moment_notes, lazy="selectin", viewonly=True)

    __table_args__ = (
        Index("ix_moments_user_day", "user_id", "day_date"),
        Index("ix_moments_user_occurred", "user_id", "occurred_at"),
    )

    def to_dict(self, *, include_links: bool = True) -> dict:
        result: dict = {
            "id": self.id,
            "user_id": self.user_id,
            "conversation_id": self.conversation_id,
            "source_message_id": self.source_message_id,
            "day_date": self.day_date.isoformat(),
            "occurred_at": self.occurred_at.isoformat(),
            "recorded_at": self.recorded_at.isoformat(),
            "content": self.content,
            "raw_excerpt": self.raw_excerpt,
            "tags": list(self.tags or []),
            "pinned": self.pinned,
        }
        if include_links:
            result["people"] = [{"id": p.id, "title": p.title} for p in (self.people or [])]
            result["places"] = [{"id": p.id, "title": p.title} for p in (self.places or [])]
            result["task_ids"] = [t.id for t in (self.tasks or [])]
            result["note_ids"] = [n.id for n in (self.notes or [])]
        return result


class MomentEmbedding(Base):
    """Embedding vector for a Moment — used by `search_journal` semantic mode.

    Stored separately from `note_embeddings` so notes-RAG and journal-RAG
    cannot cross-contaminate. This is a hard invariant of the journal design.
    """

    __tablename__ = "moment_embeddings"

    moment_id: Mapped[int] = mapped_column(
        Integer,
        ForeignKey("moments.id", ondelete="CASCADE"),
        primary_key=True,
    )
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    embedding: Mapped[list] = mapped_column(JSONB, nullable=False)
    updated_at: Mapped[datetime.datetime] = mapped_column(
        DateTime(timezone=True),
        default=lambda: datetime.datetime.now(datetime.timezone.utc),
    )
