import enum
from sqlalchemy import BigInteger, ForeignKey, Integer, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from scribe.models import Base
from scribe.models.base import SoftDeleteMixin, TimestampMixin, iso


class ProjectStatus(str, enum.Enum):
    active = "active"
    paused = "paused"
    completed = "completed"
    archived = "archived"


class Project(Base, TimestampMixin, SoftDeleteMixin):
    __tablename__ = "projects"
    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="")
    description: Mapped[str] = mapped_column(Text, default="")
    goal: Mapped[str] = mapped_column(Text, default="")
    status: Mapped[str] = mapped_column(Text, default="active")
    color: Mapped[str | None] = mapped_column(Text, nullable=True)  # hex color
    # The design system this project's UI is built from, or NULL. NULL is the
    # ordinary state, not a degraded one — most installs have no design system
    # at all and nothing may assume one exists.
    design_system_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("design_systems.id", ondelete="SET NULL"), nullable=True
    )
    # The per-project forge pin (#2778). NULL is the ordinary state: forge
    # reads resolve against the owner's keyring by repo host. When set, the
    # project's forge reads use ONLY this connection — an explicit, auditable
    # choice, constrained by the service layer to a connection the project
    # OWNER holds (never a collaborator's token).
    forge_connection_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("forge_connections.id", ondelete="SET NULL"),
        nullable=True,
    )
    # The inception record (milestone 297): what this project was decided to
    # inherit, when, and through which door — {decided_at, decided_by, via,
    # choices: {exclude_always_on_rulebooks, subscribe_rulebooks,
    # design_system_id, seed_systems}}. NULL means nobody has decided yet,
    # and enter_project asks; the effects themselves live in the subscription
    # / exclusion tables, design_system_id and the project's Systems — this is
    # the WHY, kept so later surfaces can say it. See services/inception.py.
    inception: Mapped[dict | None] = mapped_column(JSONB, nullable=True)

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "user_id": self.user_id,
            "title": self.title,
            "description": self.description,
            "goal": self.goal,
            "status": self.status,
            "color": self.color,
            "design_system_id": self.design_system_id,
            "forge_connection_id": self.forge_connection_id,
            "inception": self.inception,
            "created_at": iso(self.created_at),
            "updated_at": iso(self.updated_at),
        }
