"""initial

Revision ID: 7a3c6f95150d
Revises:
Create Date: 2026-01-01 13:40:16.060887

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa

import fastapi_users_db_sqlalchemy.generics


# revision identifiers, used by Alembic.
revision: str = "7a3c6f95150d"
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    """Upgrade schema."""
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table(
        "mcp_servers",
        sa.Column("name", sa.String(length=100), nullable=False),
        sa.Column("description", sa.String(length=500), nullable=True),
        sa.Column(
            "server_type",
            sa.Enum("STDIO", "SSE", "HTTP", name="mcpservertypeenum"),
            nullable=False,
        ),
        sa.Column("connection_config", sa.JSON(), nullable=False),
        sa.Column("is_active", sa.Boolean(), nullable=False),
        sa.Column("extra_data", sa.JSON(), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_mcp_servers")),
        sa.UniqueConstraint("name", name=op.f("uq_mcp_servers_name")),
    )
    op.create_table(
        "model_assets",
        sa.Column("asset_type", sa.String(length=50), nullable=False),
        sa.Column("identifier", sa.String(length=500), nullable=False),
        sa.Column("revision", sa.String(length=200), nullable=True),
        sa.Column("local_path", sa.String(length=1000), nullable=True),
        sa.Column("size_bytes", sa.Integer(), nullable=True),
        sa.Column(
            "status",
            sa.Enum(
                "QUEUED", "DOWNLOADING", "READY", "FAILED", name="modelassetstatusenum"
            ),
            nullable=False,
        ),
        sa.Column("error_message", sa.Text(), nullable=True),
        sa.Column("extra_data", sa.JSON(), server_default="{}", nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_model_assets")),
        sa.UniqueConstraint(
            "asset_type", "identifier", name="uq_model_assets_asset_type_identifier"
        ),
    )
    op.create_table(
        "model_providers",
        sa.Column(
            "template_id",
            sa.String(length=100),
            nullable=True,
            comment="Provider template id from built-in catalog",
        ),
        sa.Column("name", sa.String(length=100), nullable=False),
        sa.Column("display_name", sa.String(length=200), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("website", sa.String(length=500), nullable=True),
        sa.Column("api_base_url", sa.String(length=500), nullable=False),
        sa.Column("api_key", sa.String(length=500), nullable=True),
        sa.Column("interface_type", sa.String(length=50), nullable=False),
        sa.Column(
            "litellm_provider",
            sa.String(length=100),
            nullable=True,
            comment="LiteLLM custom provider name",
        ),
        sa.Column(
            "status",
            sa.Enum("ACTIVE", "INACTIVE", "MAINTENANCE", name="providerstatusenum"),
            nullable=False,
        ),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_model_providers")),
        sa.UniqueConstraint("name", name=op.f("uq_model_providers_name")),
    )
    op.create_table(
        "oauth_providers",
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("name", sa.String(length=100), nullable=False),
        sa.Column("display_name", sa.String(length=200), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("client_id", sa.String(length=500), nullable=False),
        sa.Column("client_secret", sa.String(length=1000), nullable=False),
        sa.Column("auth_url", sa.String(length=1000), nullable=False),
        sa.Column("token_url", sa.String(length=1000), nullable=False),
        sa.Column("user_info_url", sa.String(length=1000), nullable=False),
        sa.Column("scopes", sa.JSON(), nullable=False),
        sa.Column("user_mapping", sa.JSON(), nullable=False),
        sa.Column("is_active", sa.Boolean(), nullable=False),
        sa.Column("icon_url", sa.String(length=500), nullable=True),
        sa.Column("button_color", sa.String(length=20), nullable=True),
        sa.Column("sort_order", sa.Integer(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_oauth_providers")),
        sa.UniqueConstraint("name", name=op.f("uq_oauth_providers_name")),
    )
    op.create_table(
        "oauth_states",
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("state", sa.String(length=255), nullable=False),
        sa.Column("provider_name", sa.String(length=100), nullable=False),
        sa.Column("redirect_uri", sa.String(length=1000), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("extra_data", sa.JSON(), nullable=True),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_oauth_states")),
        sa.UniqueConstraint("state", name=op.f("uq_oauth_states_state")),
    )
    op.create_table(
        "system_configs",
        sa.Column(
            "key",
            sa.String(length=255),
            nullable=False,
            comment="Configuration key name",
        ),
        sa.Column(
            "value",
            sa.Text(),
            nullable=False,
            comment="Configuration value (JSON format)",
        ),
        sa.Column(
            "description", sa.Text(), nullable=True, comment="Configuration description"
        ),
        sa.Column(
            "is_public",
            sa.Boolean(),
            nullable=False,
            comment="Whether this is a public configuration",
        ),
        sa.Column(
            "is_enabled",
            sa.Boolean(),
            nullable=False,
            comment="Whether this configuration item is enabled",
        ),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_system_configs")),
    )
    with op.batch_alter_table("system_configs", schema=None) as batch_op:
        batch_op.create_index(batch_op.f("ix_system_configs_key"), ["key"], unique=True)

    op.create_table(
        "users",
        sa.Column("email", sa.String(length=320), nullable=True),
        sa.Column("name", sa.String(length=200), nullable=True),
        sa.Column("avatar_url", sa.String(length=255), nullable=True),
        sa.Column("language", sa.String(length=10), nullable=True),
        sa.Column(
            "last_verification_email_sent_at", sa.DateTime(timezone=True), nullable=True
        ),
        sa.Column(
            "last_password_reset_email_sent_at",
            sa.DateTime(timezone=True),
            nullable=True,
        ),
        sa.Column(
            "failed_login_attempts", sa.Integer(), server_default="0", nullable=False
        ),
        sa.Column("locked_until", sa.DateTime(timezone=True), nullable=True),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("hashed_password", sa.String(length=1024), nullable=False),
        sa.Column("is_active", sa.Boolean(), nullable=False),
        sa.Column("is_superuser", sa.Boolean(), nullable=False),
        sa.Column("is_verified", sa.Boolean(), nullable=False),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_users")),
        sa.UniqueConstraint("email", name=op.f("uq_users_email")),
    )
    op.create_table(
        "file_records",
        sa.Column("filename", sa.String(length=255), nullable=False),
        sa.Column("content_type", sa.String(length=100), nullable=False),
        sa.Column("file_size", sa.Integer(), nullable=False),
        sa.Column("file_type", sa.String(length=50), nullable=False),
        sa.Column("file_path", sa.String(length=1000), nullable=True),
        sa.Column("status", sa.String(length=50), nullable=False),
        sa.Column(
            "uploaded_by", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("file_metadata", sa.JSON(), nullable=True),
        sa.Column("file_hash", sa.String(length=64), nullable=True),
        sa.Column("expires_at", sa.DateTime(), nullable=True),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["uploaded_by"],
            ["users.id"],
            name=op.f("fk_file_records_uploaded_by_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_file_records")),
    )
    with op.batch_alter_table("file_records", schema=None) as batch_op:
        batch_op.create_index(
            batch_op.f("ix_file_records_file_hash"), ["file_hash"], unique=False
        )

    op.create_table(
        "models",
        sa.Column(
            "template_id",
            sa.String(length=150),
            nullable=True,
            comment="Model template id/name from built-in catalog",
        ),
        sa.Column("name", sa.String(length=200), nullable=False),
        sa.Column("display_name", sa.String(length=300), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("capabilities", sa.JSON(), nullable=False),
        sa.Column(
            "generation_config",
            sa.JSON(),
            server_default="{}",
            nullable=False,
            comment="Provider-specific generation parameters",
        ),
        sa.Column(
            "status",
            sa.Enum(
                "ACTIVE",
                "INACTIVE",
                "UNAVAILABLE",
                "DEPRECATED",
                name="modelstatusenum",
            ),
            nullable=False,
        ),
        sa.Column("max_context_tokens", sa.Integer(), nullable=True),
        sa.Column("max_output_tokens", sa.Integer(), nullable=True),
        sa.Column(
            "provider_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["provider_id"],
            ["model_providers.id"],
            name=op.f("fk_models_provider_id_model_providers"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_models")),
    )
    op.create_table(
        "oauth_user_links",
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column(
            "user_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "provider_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("provider_user_id", sa.String(length=500), nullable=False),
        sa.Column("provider_username", sa.String(length=200), nullable=True),
        sa.Column("provider_email", sa.String(length=320), nullable=True),
        sa.Column("provider_avatar", sa.String(length=1000), nullable=True),
        sa.Column("access_token", sa.Text(), nullable=True),
        sa.Column("refresh_token", sa.Text(), nullable=True),
        sa.Column("token_expires_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("raw_user_info", sa.JSON(), nullable=True),
        sa.Column("linked_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("last_login_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["provider_id"],
            ["oauth_providers.id"],
            name=op.f("fk_oauth_user_links_provider_id_oauth_providers"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["user_id"],
            ["users.id"],
            name=op.f("fk_oauth_user_links_user_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_oauth_user_links")),
        sa.UniqueConstraint(
            "provider_id", "provider_user_id", name="unique_provider_user"
        ),
        sa.UniqueConstraint("user_id", "provider_id", name="unique_user_provider"),
    )
    op.create_table(
        "refresh_tokens",
        sa.Column("token", sa.String(length=500), nullable=False),
        sa.Column(
            "user_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("revoked", sa.Boolean(), nullable=False),
        sa.Column("device_info", sa.String(length=500), nullable=True),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["user_id"],
            ["users.id"],
            name=op.f("fk_refresh_tokens_user_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_refresh_tokens")),
    )
    with op.batch_alter_table("refresh_tokens", schema=None) as batch_op:
        batch_op.create_index(
            "idx_refresh_token_active",
            ["user_id", "revoked", "expires_at"],
            unique=False,
        )
        batch_op.create_index(
            "idx_refresh_token_user_expires", ["user_id", "expires_at"], unique=False
        )
        batch_op.create_index(
            batch_op.f("ix_refresh_tokens_expires_at"), ["expires_at"], unique=False
        )
        batch_op.create_index(
            batch_op.f("ix_refresh_tokens_token"), ["token"], unique=True
        )
        batch_op.create_index(
            batch_op.f("ix_refresh_tokens_user_id"), ["user_id"], unique=False
        )

    op.create_table(
        "assistants",
        sa.Column("name", sa.String(length=200), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("system_prompt", sa.Text(), nullable=True),
        sa.Column("avatar_file_path", sa.String(length=1000), nullable=True),
        sa.Column(
            "status",
            sa.Enum("ACTIVE", "INACTIVE", "DRAFT", name="assistantstatusenum"),
            nullable=False,
        ),
        sa.Column(
            "mode",
            sa.Enum("GENERAL", "RAG", name="assistantmodeenum"),
            server_default="GENERAL",
            nullable=False,
            comment="Assistant mode: GENERAL or RAG",
        ),
        sa.Column(
            "model_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "owner_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("config", sa.JSON(), nullable=False),
        sa.Column(
            "followup_questions_enabled",
            sa.Boolean(),
            server_default="true",
            nullable=False,
            comment="If true, allow follow-up question suggestions for this assistant (when globally enabled)",
        ),
        sa.Column(
            "followup_questions_count",
            sa.Integer(),
            nullable=True,
            comment="Preferred follow-up question count (capped by global setting)",
        ),
        sa.Column(
            "use_global_generation_defaults",
            sa.Boolean(),
            server_default="true",
            nullable=False,
            comment="If true, inherit global generation defaults; if false, use assistant overrides",
        ),
        sa.Column(
            "use_global_rag_defaults",
            sa.Boolean(),
            server_default="true",
            nullable=False,
            comment="If true, inherit global RAG defaults; if false, use assistant overrides",
        ),
        sa.Column(
            "temperature",
            sa.Float(),
            server_default="1.0",
            nullable=False,
            comment="Temperature for text generation (0.0-2.0)",
        ),
        sa.Column(
            "max_tokens",
            sa.Integer(),
            server_default="2048",
            nullable=False,
            comment="Maximum number of tokens to generate",
        ),
        sa.Column(
            "max_history_messages",
            sa.Integer(),
            nullable=True,
            comment="Maximum number of history messages to include in context",
        ),
        sa.Column("is_public", sa.Boolean(), nullable=False),
        sa.Column(
            "usage_count",
            sa.Integer(),
            server_default="0",
            nullable=False,
            comment="Number of times this assistant has been used",
        ),
        sa.Column(
            "enable_agent",
            sa.Boolean(),
            server_default="false",
            nullable=False,
            comment="Whether to enable agent (tool calling) capabilities",
        ),
        sa.Column(
            "agent_max_iterations",
            sa.Integer(),
            server_default="5",
            nullable=False,
            comment="Maximum iterations for agent reasoning",
        ),
        sa.Column(
            "agent_enabled_tools",
            sa.JSON(),
            server_default="[]",
            nullable=False,
            comment="List of enabled tool names for agent",
        ),
        sa.Column(
            "agent_tool_config",
            sa.JSON(),
            server_default="{}",
            nullable=False,
            comment="Tool configuration parameters for agent",
        ),
        sa.Column("rag_config", sa.JSON(), nullable=False),
        sa.Column("knowledge_base_ids", sa.JSON(), nullable=False),
        sa.Column(
            "translations",
            sa.JSON(),
            nullable=True,
            comment="Multi-language translations for name, description, and system_prompt",
        ),
        sa.Column(
            "category",
            sa.String(length=50),
            nullable=True,
            comment="Primary category of the assistant",
        ),
        sa.Column(
            "tags",
            sa.JSON(),
            server_default="[]",
            nullable=False,
            comment="List of tags for filtering",
        ),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["model_id"],
            ["models.id"],
            name=op.f("fk_assistants_model_id_models"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"],
            ["users.id"],
            name=op.f("fk_assistants_owner_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_assistants")),
    )
    with op.batch_alter_table("assistants", schema=None) as batch_op:
        batch_op.create_index(
            batch_op.f("ix_assistants_category"), ["category"], unique=False
        )

    op.create_table(
        "knowledge_bases",
        sa.Column("name", sa.String(length=200), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column(
            "embedding_model_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=True,
        ),
        sa.Column("chunk_size", sa.Integer(), nullable=False),
        sa.Column("chunk_overlap", sa.Integer(), nullable=False),
        sa.Column("reindex_required", sa.Boolean(), nullable=False),
        sa.Column("embedding_config", sa.JSON(), nullable=False),
        sa.Column(
            "owner_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("status", sa.String(length=20), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["embedding_model_id"],
            ["models.id"],
            name=op.f("fk_knowledge_bases_embedding_model_id_models"),
            ondelete="SET NULL",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"],
            ["users.id"],
            name=op.f("fk_knowledge_bases_owner_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_knowledge_bases")),
    )
    op.create_table(
        "assistant_mcp_servers",
        sa.Column(
            "assistant_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "mcp_server_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.ForeignKeyConstraint(
            ["assistant_id"],
            ["assistants.id"],
            name=op.f("fk_assistant_mcp_servers_assistant_id_assistants"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["mcp_server_id"],
            ["mcp_servers.id"],
            name=op.f("fk_assistant_mcp_servers_mcp_server_id_mcp_servers"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint(
            "assistant_id", "mcp_server_id", name=op.f("pk_assistant_mcp_servers")
        ),
    )
    op.create_table(
        "conversations",
        sa.Column("title", sa.String(length=500), nullable=False),
        sa.Column(
            "status",
            sa.Enum("ACTIVE", "ARCHIVED", "DELETED", name="conversationstatusenum"),
            nullable=False,
        ),
        sa.Column(
            "assistant_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "user_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("last_message_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("message_count", sa.Integer(), nullable=False),
        sa.Column("extra_data", sa.JSON(), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["assistant_id"],
            ["assistants.id"],
            name=op.f("fk_conversations_assistant_id_assistants"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["user_id"],
            ["users.id"],
            name=op.f("fk_conversations_user_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_conversations")),
    )
    op.create_table(
        "documents",
        sa.Column("title", sa.String(length=500), nullable=False),
        sa.Column("content", sa.Text(), nullable=False),
        sa.Column("source_url", sa.String(length=1000), nullable=True),
        sa.Column("file_path", sa.String(length=1000), nullable=True),
        sa.Column("file_type", sa.String(length=50), nullable=True),
        sa.Column("file_hash", sa.String(length=64), nullable=True),
        sa.Column("doc_metadata", sa.JSON(), nullable=False),
        sa.Column(
            "knowledge_base_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=False,
        ),
        sa.Column(
            "owner_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("status", sa.String(length=20), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["knowledge_base_id"],
            ["knowledge_bases.id"],
            name=op.f("fk_documents_knowledge_base_id_knowledge_bases"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"],
            ["users.id"],
            name=op.f("fk_documents_owner_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_documents")),
    )
    with op.batch_alter_table("documents", schema=None) as batch_op:
        batch_op.create_index(
            batch_op.f("ix_documents_file_hash"), ["file_hash"], unique=False
        )

    op.create_table(
        "ingestion_profiles",
        sa.Column(
            "knowledge_base_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=True,
        ),
        sa.Column(
            "owner_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("name", sa.String(length=200), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("file_types", sa.JSON(), nullable=False),
        sa.Column("is_default", sa.Boolean(), nullable=False),
        sa.Column("is_builtin", sa.Boolean(), nullable=False),
        sa.Column("config", sa.JSON(), nullable=False),
        sa.Column("status", sa.String(length=20), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["knowledge_base_id"],
            ["knowledge_bases.id"],
            name=op.f("fk_ingestion_profiles_knowledge_base_id_knowledge_bases"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"],
            ["users.id"],
            name=op.f("fk_ingestion_profiles_owner_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_ingestion_profiles")),
    )
    with op.batch_alter_table("ingestion_profiles", schema=None) as batch_op:
        batch_op.create_index(
            batch_op.f("ix_ingestion_profiles_knowledge_base_id"),
            ["knowledge_base_id"],
            unique=False,
        )

    op.create_table(
        "pinned_assistants",
        sa.Column(
            "user_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "assistant_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["assistant_id"],
            ["assistants.id"],
            name=op.f("fk_pinned_assistants_assistant_id_assistants"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["user_id"],
            ["users.id"],
            name=op.f("fk_pinned_assistants_user_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_pinned_assistants")),
        sa.UniqueConstraint("user_id", "assistant_id", name="uq_user_assistant_pin"),
    )
    op.create_table(
        "agent_executions",
        sa.Column(
            "conversation_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=False,
        ),
        sa.Column("input_text", sa.Text(), nullable=False),
        sa.Column("output_text", sa.Text(), nullable=True),
        sa.Column(
            "status",
            sa.Enum(
                "RUNNING",
                "COMPLETED",
                "FAILED",
                "TIMEOUT",
                name="agentexecutionstatusenum",
            ),
            nullable=False,
        ),
        sa.Column("thought_chain", sa.JSON(), nullable=False),
        sa.Column("token_usage", sa.JSON(), nullable=False),
        sa.Column("error_message", sa.Text(), nullable=True),
        sa.Column("extra_data", sa.JSON(), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["conversation_id"],
            ["conversations.id"],
            name=op.f("fk_agent_executions_conversation_id_conversations"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_agent_executions")),
    )
    op.create_table(
        "document_chunks",
        sa.Column("content", sa.Text(), nullable=False),
        sa.Column("chunk_index", sa.Integer(), nullable=False),
        sa.Column("token_count", sa.Integer(), nullable=True),
        sa.Column(
            "document_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("chunk_metadata", sa.JSON(), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["document_id"],
            ["documents.id"],
            name=op.f("fk_document_chunks_document_id_documents"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_document_chunks")),
    )
    op.create_table(
        "ingestion_jobs",
        sa.Column(
            "knowledge_base_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=False,
        ),
        sa.Column(
            "owner_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("file_path", sa.String(length=1000), nullable=False),
        sa.Column("original_filename", sa.String(length=500), nullable=False),
        sa.Column("file_type", sa.String(length=50), nullable=True),
        sa.Column("file_hash", sa.String(length=64), nullable=True),
        sa.Column("status", sa.String(length=20), nullable=False),
        sa.Column("stage", sa.String(length=30), nullable=True),
        sa.Column("progress", sa.Integer(), nullable=False),
        sa.Column("total_items", sa.Integer(), nullable=True),
        sa.Column("processed_items", sa.Integer(), nullable=True),
        sa.Column(
            "document_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=True
        ),
        sa.Column("chunks_created", sa.Integer(), nullable=True),
        sa.Column("error_message", sa.Text(), nullable=True),
        sa.Column("error_details", sa.JSON(), nullable=True),
        sa.Column("job_config", sa.JSON(), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["document_id"],
            ["documents.id"],
            name=op.f("fk_ingestion_jobs_document_id_documents"),
            ondelete="SET NULL",
        ),
        sa.ForeignKeyConstraint(
            ["knowledge_base_id"],
            ["knowledge_bases.id"],
            name=op.f("fk_ingestion_jobs_knowledge_base_id_knowledge_bases"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"],
            ["users.id"],
            name=op.f("fk_ingestion_jobs_owner_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_ingestion_jobs")),
    )
    with op.batch_alter_table("ingestion_jobs", schema=None) as batch_op:
        batch_op.create_index(
            batch_op.f("ix_ingestion_jobs_knowledge_base_id"),
            ["knowledge_base_id"],
            unique=False,
        )
        batch_op.create_index(
            batch_op.f("ix_ingestion_jobs_owner_id"), ["owner_id"], unique=False
        )
        batch_op.create_index(
            batch_op.f("ix_ingestion_jobs_status"), ["status"], unique=False
        )

    op.create_table(
        "messages",
        sa.Column("content", sa.Text(), nullable=False),
        sa.Column(
            "message_type",
            sa.Enum("USER", "ASSISTANT", "SYSTEM", name="messagetypeenum"),
            nullable=False,
        ),
        sa.Column(
            "conversation_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=False,
        ),
        sa.Column(
            "user_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=True
        ),
        sa.Column("extra_data", sa.JSON(), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["conversation_id"],
            ["conversations.id"],
            name=op.f("fk_messages_conversation_id_conversations"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["user_id"],
            ["users.id"],
            name=op.f("fk_messages_user_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_messages")),
    )
    op.create_table(
        "conversation_shares",
        sa.Column(
            "conversation_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=False,
        ),
        sa.Column(
            "created_by", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "scope",
            sa.Enum("CONVERSATION", "MESSAGE", name="conversationsharescopeenum"),
            nullable=False,
        ),
        sa.Column(
            "start_message_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=True,
        ),
        sa.Column(
            "end_message_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=True
        ),
        sa.Column(
            "status",
            sa.Enum("ACTIVE", "REVOKED", name="conversationsharestatusenum"),
            nullable=False,
        ),
        sa.Column("view_count", sa.Integer(), nullable=False),
        sa.Column("last_accessed_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("extra_data", sa.JSON(), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["conversation_id"],
            ["conversations.id"],
            name=op.f("fk_conversation_shares_conversation_id_conversations"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["created_by"],
            ["users.id"],
            name=op.f("fk_conversation_shares_created_by_users"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["end_message_id"],
            ["messages.id"],
            name=op.f("fk_conversation_shares_end_message_id_messages"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["start_message_id"],
            ["messages.id"],
            name=op.f("fk_conversation_shares_start_message_id_messages"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_conversation_shares")),
    )
    op.create_table(
        "ingestion_job_events",
        sa.Column(
            "job_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column("seq", sa.Integer(), nullable=False),
        sa.Column("event_type", sa.String(length=50), nullable=False),
        sa.Column("payload", sa.JSON(), nullable=False),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["job_id"],
            ["ingestion_jobs.id"],
            name=op.f("fk_ingestion_job_events_job_id_ingestion_jobs"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_ingestion_job_events")),
    )
    with op.batch_alter_table("ingestion_job_events", schema=None) as batch_op:
        batch_op.create_index(
            batch_op.f("ix_ingestion_job_events_job_id"), ["job_id"], unique=False
        )
        batch_op.create_index(
            "ix_ingestion_job_events_job_seq", ["job_id", "seq"], unique=False
        )

    op.create_table(
        "message_feedbacks",
        sa.Column(
            "message_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "conversation_id",
            fastapi_users_db_sqlalchemy.generics.GUID(),
            nullable=False,
        ),
        sa.Column(
            "assistant_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "user_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False
        ),
        sa.Column(
            "rating",
            sa.Enum("UP", "DOWN", name="messagefeedbackratingenum"),
            nullable=False,
        ),
        sa.Column(
            "reasons",
            sa.JSON(),
            server_default="[]",
            nullable=False,
            comment="User-selected reason tags (UI quick options)",
        ),
        sa.Column(
            "comment", sa.Text(), nullable=True, comment="Optional free-text feedback"
        ),
        sa.Column(
            "extra_data",
            sa.JSON(),
            server_default="{}",
            nullable=False,
            comment="Optional structured metadata for analytics",
        ),
        sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(
            ["assistant_id"],
            ["assistants.id"],
            name=op.f("fk_message_feedbacks_assistant_id_assistants"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["conversation_id"],
            ["conversations.id"],
            name=op.f("fk_message_feedbacks_conversation_id_conversations"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["message_id"],
            ["messages.id"],
            name=op.f("fk_message_feedbacks_message_id_messages"),
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["user_id"],
            ["users.id"],
            name=op.f("fk_message_feedbacks_user_id_users"),
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id", name=op.f("pk_message_feedbacks")),
        sa.UniqueConstraint(
            "message_id", "user_id", name="uq_message_feedback_message_user"
        ),
    )
    with op.batch_alter_table("message_feedbacks", schema=None) as batch_op:
        batch_op.create_index(
            batch_op.f("ix_message_feedbacks_assistant_id"),
            ["assistant_id"],
            unique=False,
        )
        batch_op.create_index(
            batch_op.f("ix_message_feedbacks_conversation_id"),
            ["conversation_id"],
            unique=False,
        )
        batch_op.create_index(
            batch_op.f("ix_message_feedbacks_message_id"), ["message_id"], unique=False
        )
        batch_op.create_index(
            batch_op.f("ix_message_feedbacks_user_id"), ["user_id"], unique=False
        )

    # ### end Alembic commands ###


def downgrade() -> None:
    """Downgrade schema."""
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table("message_feedbacks", schema=None) as batch_op:
        batch_op.drop_index(batch_op.f("ix_message_feedbacks_user_id"))
        batch_op.drop_index(batch_op.f("ix_message_feedbacks_message_id"))
        batch_op.drop_index(batch_op.f("ix_message_feedbacks_conversation_id"))
        batch_op.drop_index(batch_op.f("ix_message_feedbacks_assistant_id"))

    op.drop_table("message_feedbacks")
    with op.batch_alter_table("ingestion_job_events", schema=None) as batch_op:
        batch_op.drop_index("ix_ingestion_job_events_job_seq")
        batch_op.drop_index(batch_op.f("ix_ingestion_job_events_job_id"))

    op.drop_table("ingestion_job_events")
    op.drop_table("conversation_shares")
    op.drop_table("messages")
    with op.batch_alter_table("ingestion_jobs", schema=None) as batch_op:
        batch_op.drop_index(batch_op.f("ix_ingestion_jobs_status"))
        batch_op.drop_index(batch_op.f("ix_ingestion_jobs_owner_id"))
        batch_op.drop_index(batch_op.f("ix_ingestion_jobs_knowledge_base_id"))

    op.drop_table("ingestion_jobs")
    op.drop_table("document_chunks")
    op.drop_table("agent_executions")
    op.drop_table("pinned_assistants")
    with op.batch_alter_table("ingestion_profiles", schema=None) as batch_op:
        batch_op.drop_index(batch_op.f("ix_ingestion_profiles_knowledge_base_id"))

    op.drop_table("ingestion_profiles")
    with op.batch_alter_table("documents", schema=None) as batch_op:
        batch_op.drop_index(batch_op.f("ix_documents_file_hash"))

    op.drop_table("documents")
    op.drop_table("conversations")
    op.drop_table("assistant_mcp_servers")
    op.drop_table("knowledge_bases")
    with op.batch_alter_table("assistants", schema=None) as batch_op:
        batch_op.drop_index(batch_op.f("ix_assistants_category"))

    op.drop_table("assistants")
    with op.batch_alter_table("refresh_tokens", schema=None) as batch_op:
        batch_op.drop_index(batch_op.f("ix_refresh_tokens_user_id"))
        batch_op.drop_index(batch_op.f("ix_refresh_tokens_token"))
        batch_op.drop_index(batch_op.f("ix_refresh_tokens_expires_at"))
        batch_op.drop_index("idx_refresh_token_user_expires")
        batch_op.drop_index("idx_refresh_token_active")

    op.drop_table("refresh_tokens")
    op.drop_table("oauth_user_links")
    op.drop_table("models")
    with op.batch_alter_table("file_records", schema=None) as batch_op:
        batch_op.drop_index(batch_op.f("ix_file_records_file_hash"))

    op.drop_table("file_records")
    op.drop_table("users")
    with op.batch_alter_table("system_configs", schema=None) as batch_op:
        batch_op.drop_index(batch_op.f("ix_system_configs_key"))

    op.drop_table("system_configs")
    op.drop_table("oauth_states")
    op.drop_table("oauth_providers")
    op.drop_table("model_providers")
    op.drop_table("model_assets")
    op.drop_table("mcp_servers")
    # ### end Alembic commands ###
