import datetime

from sqlalchemy import Date, DateTime, ForeignKey, Index, Integer, Text
from sqlalchemy import inspect
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship

from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin, TimestampMixin


class Conversation(Base, TimestampMixin):
    __tablename__ = "conversations"

    id: Mapped[int] = mapped_column(primary_key=True)
    user_id: Mapped[int | None] = mapped_column(
        Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
    )
    title: Mapped[str] = mapped_column(Text, default="")
    model: Mapped[str] = mapped_column(Text, default="")
    # 'chat' (default) or 'journal'. Journal conversations are day-anchored
    # and rendered by the /journal view; they are hidden from the main chat list.
    conversation_type: Mapped[str] = mapped_column(Text, default="chat", server_default="chat")
    # For journal conversations only: the calendar date this conversation covers.
    day_date: Mapped[datetime.date | None] = mapped_column(Date, nullable=True)
    # NULL = orphan notes only; -1 = all notes; positive int = specific project
    rag_project_id: Mapped[int | None] = mapped_column(Integer, nullable=True, default=None)
    # Curator scheduler bookkeeping (Phase 2, Fable #172). NULL = never run;
    # scheduler processes journal conversations where this is NULL OR older
    # than the most recent user message. See services/curator_scheduler.py.
    last_curator_run_at: Mapped[datetime.datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True, default=None
    )
    # Curator's most recent one-line summary of what was captured (Phase 3,
    # Fable #172). Injected into the next chat turn's system prompt so the
    # chat model stays aware of recent topics without needing tools to
    # retrieve. ≤ 240 chars enforced by curator service. Last-write-wins;
    # we don't keep history of prior summaries.
    curator_summary: Mapped[str | None] = mapped_column(Text, nullable=True, default=None)

    messages: Mapped[list["Message"]] = relationship(
        back_populates="conversation",
        cascade="all, delete-orphan",
        order_by="Message.created_at",
    )

    __table_args__ = (
        Index("ix_conversations_updated_at", "updated_at"),
        Index("ix_conversations_user_id", "user_id"),
    )

    def to_dict(self) -> dict:
        # Use loaded messages if available, otherwise 0
        if "messages" in inspect(self).dict:
            msg_count = len(self.messages) if self.messages else 0
        else:
            msg_count = 0
        return {
            "id": self.id,
            "title": self.title,
            "model": self.model,
            "conversation_type": self.conversation_type,
            "day_date": self.day_date.isoformat() if self.day_date else None,
            "rag_project_id": self.rag_project_id,
            "message_count": msg_count,
            "created_at": self.created_at.isoformat(),
            "updated_at": self.updated_at.isoformat(),
        }


class Message(Base, CreatedAtMixin):
    __tablename__ = "messages"

    id: Mapped[int] = mapped_column(primary_key=True)
    conversation_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("conversations.id", ondelete="CASCADE")
    )
    role: Mapped[str] = mapped_column(Text)
    content: Mapped[str] = mapped_column(Text, default="")
    status: Mapped[str] = mapped_column(Text, default="complete")
    context_note_id: Mapped[int | None] = mapped_column(
        Integer, ForeignKey("notes.id", ondelete="SET NULL"), nullable=True
    )
    tool_calls: Mapped[list | None] = mapped_column(JSONB, nullable=True)
    # 'metadata' is reserved by SQLAlchemy Declarative — use msg_metadata as the
    # Python attribute name, mapped to the 'metadata' DB column.
    msg_metadata: Mapped[dict | None] = mapped_column("metadata", JSONB, nullable=True)

    conversation: Mapped["Conversation"] = relationship(back_populates="messages")

    __table_args__ = (
        Index("ix_messages_conversation_id", "conversation_id"),
    )

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "conversation_id": self.conversation_id,
            "role": self.role,
            "content": self.content,
            "status": self.status,
            "context_note_id": self.context_note_id,
            "tool_calls": self.tool_calls,
            "metadata": self.msg_metadata,
            "created_at": self.created_at.isoformat(),
        }
