import uuid
from datetime import datetime

from sqlalchemy import (
    BigInteger,
    Boolean,
    CheckConstraint,
    DateTime,
    ForeignKey,
    ForeignKeyConstraint,
    Index,
    String,
    Text,
    UniqueConstraint,
    text,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship

from api.models.base import Base, TimestampMixin, new_uuid


FORGE_IDENTITY_STATES = ("unverified", "verified")
REPOSITORY_SLUG_KINDS = ("canonical", "alias")
REPOSITORY_RENAME_PHASES = (
    "draft",
    "preflighted",
    "forge-renamed",
    "statehub-rebound",
    "source-synced",
    "consumers-verified",
    "completed",
    "rollback-preflight",
    "rolled-back",
)
TERMINAL_REPOSITORY_RENAME_PHASES = ("completed", "rolled-back")


class RepositoryForgeIdentity(Base, TimestampMixin):
    """One explicitly verified (or explicitly unverified) Forge identity per repo."""

    __tablename__ = "repository_forge_identities"
    __table_args__ = (
        UniqueConstraint("repo_id", name="uq_repository_forge_identities_repo_id"),
        UniqueConstraint(
            "id",
            "repo_id",
            "verification_state",
            name="uq_repository_forge_identity_verified_ref",
        ),
        UniqueConstraint(
            "provider",
            "forge_instance",
            "forge_owner",
            "forge_repository_id",
            name="uq_repository_forge_identity_tuple",
        ),
        CheckConstraint(
            "verification_state IN ('unverified', 'verified')",
            name="ck_repository_forge_identity_state",
        ),
        CheckConstraint(
            "forge_repository_id IS NULL OR forge_repository_id > 0",
            name="ck_repository_forge_id_positive",
        ),
        CheckConstraint(
            "verification_state != 'verified' OR "
            "(provider IS NOT NULL AND forge_instance IS NOT NULL "
            "AND forge_owner IS NOT NULL AND forge_repository_id IS NOT NULL "
            "AND verified_at IS NOT NULL AND verified_by IS NOT NULL)",
            name="ck_repository_forge_verified_complete",
        ),
        CheckConstraint(
            "verification_state = 'verified' OR "
            "(verified_at IS NULL AND verified_by IS NULL)",
            name="ck_repository_forge_unverified_has_no_attestation",
        ),
    )

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), primary_key=True, default=new_uuid
    )
    repo_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("managed_repos.id", ondelete="RESTRICT"),
        nullable=False,
    )
    provider: Mapped[str | None] = mapped_column(String(40), nullable=True)
    forge_instance: Mapped[str | None] = mapped_column(String(255), nullable=True)
    forge_owner: Mapped[str | None] = mapped_column(String(255), nullable=True)
    forge_repository_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
    verification_state: Mapped[str] = mapped_column(
        String(20), nullable=False, default="unverified", server_default="unverified"
    )
    verified_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    verified_by: Mapped[str | None] = mapped_column(String(160), nullable=True)
    verification_evidence: Mapped[dict] = mapped_column(
        JSONB, nullable=False, default=dict, server_default="{}"
    )

    repo: Mapped["ManagedRepo"] = relationship(  # noqa: F821
        "ManagedRepo", back_populates="forge_identity", lazy="selectin"
    )
    rename_operations: Mapped[list["RepositoryRenameOperation"]] = relationship(
        "RepositoryRenameOperation",
        back_populates="forge_identity",
        lazy="selectin",
        viewonly=True,
    )


class RepositoryRenameOperation(Base, TimestampMixin):
    """Append-preserving journal for one phased canonical-coordinate change."""

    __tablename__ = "repository_rename_operations"
    __table_args__ = (
        CheckConstraint(
            "phase IN ('draft', 'preflighted', 'forge-renamed', "
            "'statehub-rebound', 'source-synced', 'consumers-verified', "
            "'completed', 'rollback-preflight', 'rolled-back')",
            name="ck_repository_rename_phase",
        ),
        CheckConstraint(
            "old_slug != new_slug", name="ck_repository_rename_distinct_slugs"
        ),
        CheckConstraint(
            "expected_forge_repository_id > 0",
            name="ck_repository_rename_forge_id_positive",
        ),
        CheckConstraint(
            "forge_identity_state = 'verified'",
            name="ck_repository_rename_verified_identity",
        ),
        CheckConstraint(
            "phase = 'draft' OR preflighted_at IS NOT NULL",
            name="ck_repository_rename_preflight_timestamp",
        ),
        CheckConstraint(
            "phase != 'completed' OR completed_at IS NOT NULL",
            name="ck_repository_rename_completed_timestamp",
        ),
        CheckConstraint(
            "phase != 'rolled-back' OR rolled_back_at IS NOT NULL",
            name="ck_repository_rename_rollback_timestamp",
        ),
        Index(
            "uq_repository_rename_active_repo",
            "repo_id",
            unique=True,
            postgresql_where=text("phase NOT IN ('completed', 'rolled-back')"),
        ),
        Index(
            "uq_repository_rename_active_new_slug",
            "new_slug",
            unique=True,
            postgresql_where=text("phase NOT IN ('completed', 'rolled-back')"),
        ),
        Index(
            "ix_repository_rename_phase", "phase"
        ),
        ForeignKeyConstraint(
            ["forge_identity_id", "repo_id", "forge_identity_state"],
            [
                "repository_forge_identities.id",
                "repository_forge_identities.repo_id",
                "repository_forge_identities.verification_state",
            ],
            name="fk_repository_rename_verified_identity",
            ondelete="RESTRICT",
        ),
    )

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), primary_key=True, default=new_uuid
    )
    repo_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("managed_repos.id", ondelete="RESTRICT"),
        nullable=False,
        index=True,
    )
    forge_identity_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), nullable=False
    )
    forge_identity_state: Mapped[str] = mapped_column(
        String(20), nullable=False, default="verified", server_default="verified"
    )
    expected_provider: Mapped[str] = mapped_column(String(40), nullable=False)
    expected_forge_instance: Mapped[str] = mapped_column(String(255), nullable=False)
    expected_forge_owner: Mapped[str] = mapped_column(String(255), nullable=False)
    expected_forge_repository_id: Mapped[int] = mapped_column(
        BigInteger, nullable=False
    )
    expected_source_commit: Mapped[str] = mapped_column(String(64), nullable=False)
    expected_default_branch: Mapped[str] = mapped_column(String(255), nullable=False)
    old_slug: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
    new_slug: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
    old_coordinates: Mapped[dict] = mapped_column(JSONB, nullable=False)
    new_coordinates: Mapped[dict] = mapped_column(JSONB, nullable=False)
    phase: Mapped[str] = mapped_column(
        String(32), nullable=False, default="draft", server_default="draft"
    )
    actor: Mapped[str] = mapped_column(String(160), nullable=False)
    phase_changed_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False
    )
    preflighted_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    preflight_expires_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    completed_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    rolled_back_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    evidence: Mapped[dict] = mapped_column(
        JSONB, nullable=False, default=dict, server_default="{}"
    )
    error_code: Mapped[str | None] = mapped_column(String(80), nullable=True)
    error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
    error_details: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    error_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )

    repo: Mapped["ManagedRepo"] = relationship(  # noqa: F821
        "ManagedRepo", back_populates="rename_operations", lazy="selectin"
    )
    forge_identity: Mapped[RepositoryForgeIdentity] = relationship(
        "RepositoryForgeIdentity",
        back_populates="rename_operations",
        lazy="selectin",
        viewonly=True,
    )
    slugs: Mapped[list["RepositorySlug"]] = relationship(
        "RepositorySlug", back_populates="source_operation", lazy="selectin"
    )


class RepositorySlug(Base, TimestampMixin):
    """Global uniqueness boundary for current and protected prior slugs."""

    __tablename__ = "repository_slugs"
    __table_args__ = (
        UniqueConstraint("slug", name="uq_repository_slugs_slug"),
        CheckConstraint(
            "kind IN ('canonical', 'alias')", name="ck_repository_slug_kind"
        ),
        CheckConstraint(
            "kind != 'alias' OR protected",
            name="ck_repository_slug_alias_protected",
        ),
        Index(
            "uq_repository_slugs_one_canonical",
            "repo_id",
            unique=True,
            postgresql_where=text("kind = 'canonical'"),
        ),
        Index("ix_repository_slugs_repo_kind", "repo_id", "kind"),
    )

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), primary_key=True, default=new_uuid
    )
    repo_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("managed_repos.id", ondelete="RESTRICT"),
        nullable=False,
    )
    slug: Mapped[str] = mapped_column(String(100), nullable=False)
    kind: Mapped[str] = mapped_column(String(16), nullable=False)
    protected: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=True, server_default="true"
    )
    source_operation_id: Mapped[uuid.UUID | None] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("repository_rename_operations.id", ondelete="RESTRICT"),
        nullable=True,
        index=True,
    )

    repo: Mapped["ManagedRepo"] = relationship(  # noqa: F821
        "ManagedRepo", back_populates="slug_records", lazy="selectin"
    )
    source_operation: Mapped[RepositoryRenameOperation | None] = relationship(
        "RepositoryRenameOperation", back_populates="slugs", lazy="selectin"
    )
