"""sx-pub ORM models — federated SX publishing protocol.

Tables for the sx-pub actor, content collections, published documents,
outbox activities, and federation relationships (followers/following).
"""
from __future__ import annotations

from datetime import datetime

from sqlalchemy import (
    String, Integer, DateTime, Text,
    ForeignKey, UniqueConstraint, Index, func,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship

from shared.db.base import Base


class SxPubActor(Base):
    """Singleton actor for this sx-pub instance."""
    __tablename__ = "sx_pub_actor"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    preferred_username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
    display_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
    summary: Mapped[str | None] = mapped_column(Text, nullable=True)
    public_key_pem: Mapped[str] = mapped_column(Text, nullable=False)
    private_key_pem: Mapped[str] = mapped_column(Text, nullable=False)
    domain: Mapped[str] = mapped_column(String(255), nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(),
    )

    def __repr__(self) -> str:
        return f"<SxPubActor @{self.preferred_username}>"


class SxPubCollection(Base):
    """Named grouping of published documents (e.g. core-specs, platforms)."""
    __tablename__ = "sx_pub_collections"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    slug: Mapped[str] = mapped_column(String(128), unique=True, nullable=False)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(),
    )

    documents = relationship("SxPubDocument", back_populates="collection", lazy="selectin")

    def __repr__(self) -> str:
        return f"<SxPubCollection {self.slug}>"


class SxPubDocument(Base):
    """Published content — path→CID index entry."""
    __tablename__ = "sx_pub_documents"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    collection_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("sx_pub_collections.id", ondelete="CASCADE"), nullable=False,
    )
    slug: Mapped[str] = mapped_column(String(255), nullable=False)
    title: Mapped[str | None] = mapped_column(String(512), nullable=True)
    summary: Mapped[str | None] = mapped_column(Text, nullable=True)
    content_hash: Mapped[str] = mapped_column(String(128), nullable=False)
    ipfs_cid: Mapped[str | None] = mapped_column(String(128), nullable=True)
    size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True)
    requires: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    status: Mapped[str] = mapped_column(
        String(20), nullable=False, default="draft", server_default="draft",
    )
    published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(),
    )

    collection = relationship("SxPubCollection", back_populates="documents")

    __table_args__ = (
        UniqueConstraint("collection_id", "slug", name="uq_pub_doc_collection_slug"),
        Index("ix_pub_doc_collection", "collection_id"),
        Index("ix_pub_doc_status", "status"),
        Index("ix_pub_doc_cid", "ipfs_cid"),
    )

    def __repr__(self) -> str:
        return f"<SxPubDocument {self.slug} cid={self.ipfs_cid}>"


class SxPubActivity(Base):
    """Outbox activity (Publish, Follow, Announce, Anchor)."""
    __tablename__ = "sx_pub_activities"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    activity_type: Mapped[str] = mapped_column(String(64), nullable=False)
    object_type: Mapped[str | None] = mapped_column(String(64), nullable=True)
    object_data: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    ipfs_cid: Mapped[str | None] = mapped_column(String(128), nullable=True)
    published: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(),
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(),
    )

    __table_args__ = (
        Index("ix_pub_activity_type", "activity_type"),
        Index("ix_pub_activity_published", "published"),
    )

    def __repr__(self) -> str:
        return f"<SxPubActivity {self.activity_type}>"


class SxPubFollower(Base):
    """Remote server that follows us."""
    __tablename__ = "sx_pub_followers"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    follower_acct: Mapped[str] = mapped_column(String(512), nullable=False)
    follower_inbox: Mapped[str] = mapped_column(String(512), nullable=False)
    follower_actor_url: Mapped[str] = mapped_column(String(512), nullable=False)
    follower_public_key: Mapped[str | None] = mapped_column(Text, nullable=True)
    state: Mapped[str] = mapped_column(
        String(20), nullable=False, default="accepted", server_default="accepted",
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(),
    )

    __table_args__ = (
        UniqueConstraint("follower_acct", name="uq_pub_follower_acct"),
        Index("ix_pub_follower_state", "state"),
    )

    def __repr__(self) -> str:
        return f"<SxPubFollower {self.follower_acct}>"


class SxPubFollowing(Base):
    """Remote server we follow."""
    __tablename__ = "sx_pub_following"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    remote_actor_url: Mapped[str] = mapped_column(String(512), unique=True, nullable=False)
    remote_inbox: Mapped[str] = mapped_column(String(512), nullable=False)
    state: Mapped[str] = mapped_column(
        String(20), nullable=False, default="pending", server_default="pending",
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(),
    )
    accepted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    __table_args__ = (
        Index("ix_pub_following_state", "state"),
    )

    def __repr__(self) -> str:
        return f"<SxPubFollowing {self.remote_actor_url}>"
