"""initial_schema

Revision ID: 357b02e6ee7e
Revises: 
Create Date: 2025-12-18 09:25:04.075974+00:00

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = '357b02e6ee7e'
down_revision = None
branch_labels = None
depends_on = None


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('agents',
    sa.Column('agent_name', sa.String(length=255), nullable=False, comment='Unique agent name'),
    sa.Column('agent_type', sa.String(length=50), nullable=False, comment='Agent type (llm, research, code)'),
    sa.Column('description', sa.Text(), nullable=True, comment='Agent description'),
    sa.Column('system_prompt', sa.Text(), nullable=True, comment='System prompt for the agent'),
    sa.Column('llm_config', sa.JSON(), nullable=False, comment='LLM configuration (provider, model, api_key, etc.)'),
    sa.Column('tools_config', sa.JSON(), nullable=True, comment='Tools configuration'),
    sa.Column('agent_metadata', sa.JSON(), nullable=True, comment='Additional agent metadata'),
    sa.Column('observability_config', sa.JSON(), nullable=True, comment='Observability configuration (Langfuse tracing, sampling rate, etc.)'),
    sa.Column('suggestion_prompts', sa.JSON(), nullable=True, comment='Suggestion prompts for chat interface (array of objects with title, description, icon, prompt)'),
    sa.Column('voice_enabled', sa.Boolean(), nullable=False, comment='Whether voice chat is enabled for this agent'),
    sa.Column('voice_config', sa.JSON(), nullable=True, comment='Voice configuration (STT/TTS providers, voice settings, etc.)'),
    sa.Column('is_public', sa.Boolean(), nullable=False, comment='Whether agent is publicly visible in marketplace'),
    sa.Column('likes_count', sa.Integer(), nullable=False, comment='Number of likes'),
    sa.Column('dislikes_count', sa.Integer(), nullable=False, comment='Number of dislikes'),
    sa.Column('avatar', sa.String(length=255), nullable=True, comment='URL to agent avatar'),
    sa.Column('usage_count', sa.Integer(), nullable=False, comment='Number of times agent has been used'),
    sa.Column('category', sa.String(length=100), nullable=True, comment='Agent category (e.g., Support, Engineering, Sales)'),
    sa.Column('tags', postgresql.ARRAY(sa.String()), nullable=True, comment='Agent tags for filtering and search'),
    sa.Column('execution_count', sa.Integer(), nullable=False, comment='Number of executions'),
    sa.Column('success_count', sa.Integer(), nullable=False, comment='Number of successful executions'),
    sa.Column('parent_agent_id', sa.UUID(), nullable=True, comment='Parent agent ID for multi-agent hierarchy'),
    sa.Column('allow_transfer', sa.Boolean(), nullable=False, comment='Whether this agent can transfer to other agents'),
    sa.Column('transfer_scope', sa.String(length=50), nullable=False, comment='Transfer scope: sub_agents, siblings, or parent'),
    sa.Column('output_key', sa.String(length=255), nullable=True, comment="State key where this agent's output is automatically saved"),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('status', sa.String(length=50), nullable=False, comment='Record status'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.ForeignKeyConstraint(['parent_agent_id'], ['agents.id'], name=op.f('agents_parent_agent_id_fkey'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('agents_pkey'))
    )
    op.create_index(op.f('agents_agent_name_idx'), 'agents', ['agent_name'], unique=True)
    op.create_index(op.f('agents_parent_agent_id_idx'), 'agents', ['parent_agent_id'], unique=False)
    op.create_index(op.f('agents_status_idx'), 'agents', ['status'], unique=False)
    op.create_index(op.f('agents_tenant_id_idx'), 'agents', ['tenant_id'], unique=False)
    op.create_table('integration_configs',
    sa.Column('tenant_id', sa.UUID(), nullable=True, comment='Tenant ID (null for super admin default settings)'),
    sa.Column('integration_type', sa.String(length=50), nullable=False, comment='Integration type: email, payment, storage, analytics, monitoring, etc.'),
    sa.Column('provider', sa.String(length=50), nullable=False, comment='Provider name: smtp, sendgrid, stripe, s3, etc.'),
    sa.Column('config_data', sa.Text(), nullable=False, comment='JSON configuration with encrypted credentials'),
    sa.Column('is_active', sa.Boolean(), nullable=False, comment='Whether this configuration is currently active'),
    sa.Column('is_default', sa.Boolean(), nullable=False, comment='Whether this is the default config for this integration type'),
    sa.Column('is_platform_config', sa.Boolean(), nullable=False, comment='Whether this is a platform-level configuration (applies to all tenants)'),
    sa.Column('created_by', sa.UUID(), nullable=True, comment='User who created this configuration'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.PrimaryKeyConstraint('id', name=op.f('integration_configs_pkey')),
    sa.UniqueConstraint('tenant_id', 'integration_type', 'provider', 'is_default', name='uq_integration_config_tenant_type_provider_default')
    )
    op.create_index(op.f('integration_configs_integration_type_idx'), 'integration_configs', ['integration_type'], unique=False)
    op.create_index(op.f('integration_configs_provider_idx'), 'integration_configs', ['provider'], unique=False)
    op.create_index(op.f('integration_configs_tenant_id_idx'), 'integration_configs', ['tenant_id'], unique=False)
    op.create_table('mcp_servers',
    sa.Column('name', sa.String(length=255), nullable=False, comment='MCP server name'),
    sa.Column('url', sa.String(length=500), nullable=True, comment='MCP server URL/endpoint (required for http transport)'),
    sa.Column('description', sa.Text(), nullable=True, comment='Server description and capabilities'),
    sa.Column('transport_type', sa.String(length=20), nullable=False, comment='Transport type: http, stdio'),
    sa.Column('command', sa.String(length=500), nullable=True, comment="Command to run for stdio transport (e.g., 'npx', 'python')"),
    sa.Column('args', sa.JSON(), nullable=True, comment="Command arguments as array (e.g., ['-y', '@modelcontextprotocol/server-github'])"),
    sa.Column('env_vars', sa.JSON(), nullable=True, comment='Environment variables for stdio process'),
    sa.Column('server_type', sa.String(length=50), nullable=False, comment='Server type (http, websocket, grpc)'),
    sa.Column('auth_type', sa.String(length=50), nullable=False, comment='Authentication type (none, api_key, oauth, bearer)'),
    sa.Column('auth_config', sa.JSON(), nullable=True, comment='Authentication configuration (encrypted)'),
    sa.Column('headers', sa.JSON(), nullable=True, comment='Custom HTTP headers to include in requests'),
    sa.Column('capabilities', sa.JSON(), nullable=True, comment='Server capabilities and available tools'),
    sa.Column('server_metadata', sa.JSON(), nullable=True, comment='Additional server metadata'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('status', sa.String(length=50), nullable=False, comment='Record status'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.PrimaryKeyConstraint('id', name=op.f('mcp_servers_pkey'))
    )
    op.create_index(op.f('mcp_servers_name_idx'), 'mcp_servers', ['name'], unique=False)
    op.create_index(op.f('mcp_servers_status_idx'), 'mcp_servers', ['status'], unique=False)
    op.create_index(op.f('mcp_servers_tenant_id_idx'), 'mcp_servers', ['tenant_id'], unique=False)
    op.create_table('permissions',
    sa.Column('name', sa.String(length=255), nullable=False, comment="Permission name (e.g., 'agents.create')"),
    sa.Column('resource', sa.String(length=100), nullable=False, comment="Resource type (e.g., 'agents', 'knowledge_bases')"),
    sa.Column('action', sa.String(length=50), nullable=False, comment="Action type (e.g., 'create', 'read', 'update', 'delete')"),
    sa.Column('description', sa.Text(), nullable=True, comment='Human-readable description of the permission'),
    sa.Column('is_system', sa.Boolean(), nullable=False, comment='Whether this is a system permission (vs custom)'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.PrimaryKeyConstraint('id', name=op.f('permissions_pkey'))
    )
    op.create_index(op.f('permissions_name_idx'), 'permissions', ['name'], unique=True)
    op.create_index(op.f('permissions_resource_idx'), 'permissions', ['resource'], unique=False)
    op.create_table('platform_settings',
    sa.Column('tenant_id', sa.UUID(), nullable=True, comment='Tenant ID (null for super admin default settings)'),
    sa.Column('stripe_secret_key', sa.Text(), nullable=True, comment='Encrypted Stripe secret key'),
    sa.Column('stripe_publishable_key', sa.String(length=500), nullable=True, comment='Stripe publishable key (not encrypted, safe to expose)'),
    sa.Column('stripe_webhook_secret', sa.Text(), nullable=True, comment='Encrypted Stripe webhook signing secret'),
    sa.Column('stripe_enabled', sa.String(length=10), nullable=False, comment='Whether Stripe integration is enabled'),
    sa.Column('payment_gateway', sa.String(length=50), nullable=False, comment='Active payment gateway: stripe, paypal, etc.'),
    sa.Column('platform_name', sa.String(length=255), nullable=True, comment='Platform name for branding'),
    sa.Column('platform_logo_url', sa.String(length=500), nullable=True, comment='Platform logo URL'),
    sa.Column('support_email', sa.String(length=255), nullable=True, comment='Platform support email'),
    sa.Column('features_enabled', sa.Text(), nullable=True, comment='JSON string of enabled platform features'),
    sa.Column('smtp_host', sa.String(length=255), nullable=True, comment='SMTP server host'),
    sa.Column('smtp_port', sa.String(length=10), nullable=True, comment='SMTP server port'),
    sa.Column('smtp_username', sa.String(length=255), nullable=True, comment='SMTP username'),
    sa.Column('smtp_password', sa.Text(), nullable=True, comment='Encrypted SMTP password'),
    sa.Column('smtp_from_email', sa.String(length=255), nullable=True, comment='Default from email address'),
    sa.Column('smtp_from_name', sa.String(length=255), nullable=True, comment='Default from name'),
    sa.Column('storage_provider', sa.String(length=50), nullable=False, comment='Storage provider: s3, minio, local, etc.'),
    sa.Column('storage_config', sa.Text(), nullable=True, comment='JSON string of storage provider configuration'),
    sa.Column('analytics_enabled', sa.String(length=10), nullable=False, comment='Whether analytics tracking is enabled'),
    sa.Column('analytics_config', sa.Text(), nullable=True, comment='JSON string of analytics configuration'),
    sa.Column('rate_limit_config', sa.Text(), nullable=True, comment='JSON string of global rate limiting configuration'),
    sa.Column('maintenance_mode', sa.String(length=10), nullable=False, comment='Whether platform is in maintenance mode'),
    sa.Column('maintenance_message', sa.Text(), nullable=True, comment='Message to display during maintenance'),
    sa.Column('app_base_url', sa.String(length=500), nullable=True, comment='Base URL of the frontend application (e.g., https://app.example.com or https://synkora.ai)'),
    sa.Column('custom_config', sa.Text(), nullable=True, comment='JSON string for additional custom configuration'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.PrimaryKeyConstraint('id', name=op.f('platform_settings_pkey'))
    )
    op.create_table('subscription_plans',
    sa.Column('name', sa.String(length=100), nullable=False, comment='Plan name (e.g., Free, Starter, Professional, Enterprise)'),
    sa.Column('tier', sa.Enum('FREE', 'STARTER', 'PROFESSIONAL', 'ENTERPRISE', name='plantier'), nullable=False, comment='Plan tier for categorization'),
    sa.Column('description', sa.Text(), nullable=True, comment='Plan description and features'),
    sa.Column('price_monthly', sa.Numeric(precision=10, scale=2), nullable=False, comment='Monthly subscription price in USD'),
    sa.Column('price_yearly', sa.Numeric(precision=10, scale=2), nullable=True, comment='Yearly subscription price in USD (discounted)'),
    sa.Column('credits_monthly', sa.Integer(), nullable=False, comment='Monthly credit allocation'),
    sa.Column('credits_rollover', sa.Boolean(), nullable=False, comment='Whether unused credits roll over to next month'),
    sa.Column('max_agents', sa.Integer(), nullable=True, comment='Maximum number of agents allowed (null = unlimited)'),
    sa.Column('max_team_members', sa.Integer(), nullable=True, comment='Maximum number of team members allowed (null = unlimited)'),
    sa.Column('max_api_calls_per_month', sa.Integer(), nullable=True, comment='Maximum API calls per month (null = unlimited)'),
    sa.Column('max_knowledge_bases', sa.Integer(), nullable=True, comment='Maximum number of knowledge bases (null = unlimited)'),
    sa.Column('max_data_sources', sa.Integer(), nullable=True, comment='Maximum number of data sources (null = unlimited)'),
    sa.Column('max_custom_tools', sa.Integer(), nullable=True, comment='Maximum number of custom tools (null = unlimited)'),
    sa.Column('max_database_connections', sa.Integer(), nullable=True, comment='Maximum number of database connections (null = unlimited)'),
    sa.Column('max_mcp_servers', sa.Integer(), nullable=True, comment='Maximum number of MCP servers (null = unlimited)'),
    sa.Column('max_scheduled_tasks', sa.Integer(), nullable=True, comment='Maximum number of scheduled tasks (null = unlimited)'),
    sa.Column('max_widgets', sa.Integer(), nullable=True, comment='Maximum number of widgets (null = unlimited)'),
    sa.Column('max_slack_bots', sa.Integer(), nullable=True, comment='Maximum number of Slack bots (null = unlimited)'),
    sa.Column('features', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='JSON object of plan features and limits'),
    sa.Column('is_active', sa.Boolean(), nullable=False, comment='Whether plan is currently available for subscription'),
    sa.Column('stripe_product_id', sa.String(length=255), nullable=True, comment='Stripe Product ID for this plan'),
    sa.Column('stripe_price_id', sa.String(length=255), nullable=True, comment='Stripe Price ID for monthly subscription'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.PrimaryKeyConstraint('id', name=op.f('subscription_plans_pkey')),
    sa.UniqueConstraint('name', name=op.f('subscription_plans_name_key'))
    )
    op.create_index(op.f('subscription_plans_is_active_idx'), 'subscription_plans', ['is_active'], unique=False)
    op.create_index(op.f('subscription_plans_tier_idx'), 'subscription_plans', ['tier'], unique=False)
    op.create_table('tenants',
    sa.Column('name', sa.String(length=255), nullable=False, comment='Tenant name'),
    sa.Column('plan', sa.Enum('FREE', 'BASIC', 'PRO', 'ENTERPRISE', name='tenantplan'), nullable=False, comment='Subscription plan'),
    sa.Column('tenant_type', sa.Enum('PLATFORM', 'EXTERNAL', name='tenanttype'), nullable=False, comment='Tenant type: PLATFORM for platform tenant, EXTERNAL for customer tenants'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('status', sa.String(length=50), nullable=False, comment='Record status'),
    sa.PrimaryKeyConstraint('id', name=op.f('tenants_pkey'))
    )
    op.create_index(op.f('tenants_status_idx'), 'tenants', ['status'], unique=False)
    op.create_table('voice_api_keys',
    sa.Column('provider', sa.String(length=50), nullable=False, comment='Voice provider (openai_whisper, openai_tts, elevenlabs)'),
    sa.Column('api_key_encrypted', sa.Text(), nullable=False, comment='Encrypted API key'),
    sa.Column('is_active', sa.Boolean(), nullable=False, comment='Whether the API key is active'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.PrimaryKeyConstraint('id', name=op.f('voice_api_keys_pkey'))
    )
    op.create_index(op.f('voice_api_keys_tenant_id_idx'), 'voice_api_keys', ['tenant_id'], unique=False)
    op.create_table('agent_api_keys',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent this API key provides access to'),
    sa.Column('key_name', sa.String(length=255), nullable=False, comment='Human-readable name for the API key'),
    sa.Column('api_key', sa.String(length=255), nullable=False, comment='Hashed API key for authentication'),
    sa.Column('key_prefix', sa.String(length=20), nullable=False, comment='First characters of the key for identification (e.g., sk_live_abc123...)'),
    sa.Column('permissions', postgresql.ARRAY(sa.String()), nullable=False, comment="List of allowed operations (e.g., ['chat', 'execute', 'stream'])"),
    sa.Column('rate_limit_per_minute', sa.Integer(), nullable=False, comment='Maximum requests per minute (0 = unlimited)'),
    sa.Column('rate_limit_per_hour', sa.Integer(), nullable=False, comment='Maximum requests per hour (0 = unlimited)'),
    sa.Column('rate_limit_per_day', sa.Integer(), nullable=False, comment='Maximum requests per day (0 = unlimited)'),
    sa.Column('allowed_ips', postgresql.ARRAY(sa.Text()), nullable=True, comment='List of IP addresses allowed to use this key (null = all IPs)'),
    sa.Column('allowed_origins', postgresql.ARRAY(sa.Text()), nullable=True, comment='List of CORS origins allowed to use this key (null = all origins)'),
    sa.Column('expires_at', sa.DateTime(timezone=True), nullable=True, comment='Optional expiration timestamp for the key'),
    sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=True, comment='Timestamp of last successful use'),
    sa.Column('is_active', sa.Boolean(), nullable=False, comment='Whether the API key is currently active'),
    sa.Column('key_metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Additional metadata (e.g., created_by, purpose, environment)'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_api_keys_agent_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_api_keys_pkey'))
    )
    op.create_index(op.f('agent_api_keys_agent_id_idx'), 'agent_api_keys', ['agent_id'], unique=False)
    op.create_index(op.f('agent_api_keys_api_key_idx'), 'agent_api_keys', ['api_key'], unique=True)
    op.create_index(op.f('agent_api_keys_expires_at_idx'), 'agent_api_keys', ['expires_at'], unique=False)
    op.create_index(op.f('agent_api_keys_is_active_idx'), 'agent_api_keys', ['is_active'], unique=False)
    op.create_index(op.f('agent_api_keys_key_prefix_idx'), 'agent_api_keys', ['key_prefix'], unique=False)
    op.create_index(op.f('agent_api_keys_tenant_id_idx'), 'agent_api_keys', ['tenant_id'], unique=False)
    op.create_table('agent_context_files',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent this file belongs to'),
    sa.Column('filename', sa.String(length=255), nullable=False, comment='Original filename'),
    sa.Column('file_type', sa.String(length=100), nullable=False, comment='File MIME type'),
    sa.Column('file_size', sa.BigInteger(), nullable=False, comment='File size in bytes'),
    sa.Column('s3_key', sa.String(length=500), nullable=False, comment='S3 storage key'),
    sa.Column('s3_bucket', sa.String(length=255), nullable=False, comment='S3 bucket name'),
    sa.Column('extracted_text', sa.Text(), nullable=True, comment='Extracted text content from the file'),
    sa.Column('extraction_status', sa.String(length=50), nullable=False, comment='Status of text extraction (PENDING, COMPLETED, FAILED)'),
    sa.Column('extraction_error', sa.Text(), nullable=True, comment='Error message if extraction failed'),
    sa.Column('display_order', sa.Integer(), nullable=False, comment='Display order (lower numbers first)'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_context_files_agent_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_context_files_pkey'))
    )
    op.create_index(op.f('agent_context_files_agent_id_idx'), 'agent_context_files', ['agent_id'], unique=False)
    op.create_index(op.f('agent_context_files_extraction_status_idx'), 'agent_context_files', ['extraction_status'], unique=False)
    op.create_index(op.f('agent_context_files_tenant_id_idx'), 'agent_context_files', ['tenant_id'], unique=False)
    op.create_table('agent_domains',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent ID'),
    sa.Column('domain', sa.String(length=255), nullable=True, comment='Custom domain name'),
    sa.Column('subdomain', sa.String(length=255), nullable=False, comment='Subdomain on platform (e.g., myagent.synkora.ai)'),
    sa.Column('is_custom_domain', sa.Boolean(), nullable=False, comment='Whether using custom domain'),
    sa.Column('is_verified', sa.Boolean(), nullable=False, comment='Whether domain is verified'),
    sa.Column('verification_token', sa.String(length=255), nullable=True, comment='Token for domain verification'),
    sa.Column('verification_method', sa.String(length=50), nullable=True, comment='Verification method (DNS, file, etc.)'),
    sa.Column('ssl_enabled', sa.Boolean(), nullable=False, comment='Whether SSL is enabled'),
    sa.Column('ssl_certificate', sa.Text(), nullable=True, comment='SSL certificate details (JSON)'),
    sa.Column('dns_records', sa.Text(), nullable=True, comment='Required DNS records (JSON)'),
    sa.Column('status', sa.String(length=50), nullable=False, comment='Domain status (pending, active, failed, etc.)'),
    sa.Column('last_verified_at', sa.DateTime(), nullable=True, comment='Last verification timestamp'),
    sa.Column('error_message', sa.Text(), nullable=True, comment='Error message if verification failed'),
    sa.Column('chat_page_config', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Chat page customization configuration'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_domains_agent_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_domains_pkey'))
    )
    op.create_index(op.f('agent_domains_agent_id_idx'), 'agent_domains', ['agent_id'], unique=False)
    op.create_index(op.f('agent_domains_domain_idx'), 'agent_domains', ['domain'], unique=True)
    op.create_index(op.f('agent_domains_subdomain_idx'), 'agent_domains', ['subdomain'], unique=True)
    op.create_index(op.f('agent_domains_tenant_id_idx'), 'agent_domains', ['tenant_id'], unique=False)
    op.create_table('agent_llm_configs',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent ID'),
    sa.Column('name', sa.String(length=255), nullable=False, comment='Display name for the LLM config'),
    sa.Column('provider', sa.String(length=50), nullable=False, comment='LLM provider (google, OPENAI, anthropic, LITELLM)'),
    sa.Column('model_name', sa.String(length=255), nullable=False, comment='Model name'),
    sa.Column('api_key', sa.Text(), nullable=False, comment='Encrypted API key'),
    sa.Column('api_base', sa.String(length=500), nullable=True, comment='API base URL for LiteLLM'),
    sa.Column('temperature', sa.Float(), nullable=True, comment='Temperature setting'),
    sa.Column('max_tokens', sa.Integer(), nullable=True, comment='Maximum tokens'),
    sa.Column('top_p', sa.Float(), nullable=True, comment='Top P setting'),
    sa.Column('additional_params', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Additional parameters'),
    sa.Column('is_default', sa.Boolean(), nullable=False, comment='Whether this is the default config'),
    sa.Column('display_order', sa.Integer(), nullable=False, comment='Display order'),
    sa.Column('enabled', sa.Boolean(), nullable=False, comment='Whether this config is enabled'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_llm_configs_agent_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_llm_configs_pkey'))
    )
    op.create_index(op.f('agent_llm_configs_agent_id_idx'), 'agent_llm_configs', ['agent_id'], unique=False)
    op.create_index(op.f('agent_llm_configs_enabled_idx'), 'agent_llm_configs', ['enabled'], unique=False)
    op.create_index(op.f('agent_llm_configs_is_default_idx'), 'agent_llm_configs', ['is_default'], unique=False)
    op.create_index(op.f('agent_llm_configs_tenant_id_idx'), 'agent_llm_configs', ['tenant_id'], unique=False)
    op.create_table('agent_mcp_servers',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('agent_id', sa.UUID(), nullable=False),
    sa.Column('mcp_server_id', sa.UUID(), nullable=False),
    sa.Column('mcp_config', sa.JSON(), nullable=True),
    sa.Column('is_active', sa.Boolean(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_mcp_servers_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['mcp_server_id'], ['mcp_servers.id'], name=op.f('agent_mcp_servers_mcp_server_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_mcp_servers_pkey'))
    )
    op.create_index(op.f('agent_mcp_servers_agent_id_idx'), 'agent_mcp_servers', ['agent_id'], unique=False)
    op.create_index(op.f('agent_mcp_servers_mcp_server_id_idx'), 'agent_mcp_servers', ['mcp_server_id'], unique=False)
    op.create_table('agent_pricing',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent ID'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Creator tenant ID'),
    sa.Column('pricing_model', sa.Enum('FREE', 'PER_USE', 'SUBSCRIPTION', name='pricingmodel'), nullable=False, comment='Pricing model type'),
    sa.Column('credits_per_use', sa.Integer(), nullable=True, comment='Credits charged per use (for PER_USE model)'),
    sa.Column('monthly_subscription_credits', sa.Integer(), nullable=True, comment='Monthly subscription cost in credits'),
    sa.Column('revenue_share_percentage', sa.Numeric(precision=5, scale=2), nullable=False, comment='Creator revenue share percentage (e.g., 70.00 for 70%)'),
    sa.Column('is_active', sa.Boolean(), nullable=False, comment='Whether pricing is active'),
    sa.Column('total_uses', sa.Integer(), nullable=False, comment='Total number of uses'),
    sa.Column('total_revenue_credits', sa.Integer(), nullable=False, comment='Total revenue earned in credits'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_pricing_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('agent_pricing_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_pricing_pkey'))
    )
    op.create_index(op.f('agent_pricing_agent_id_idx'), 'agent_pricing', ['agent_id'], unique=True)
    op.create_index(op.f('agent_pricing_is_active_idx'), 'agent_pricing', ['is_active'], unique=False)
    op.create_index(op.f('agent_pricing_pricing_model_idx'), 'agent_pricing', ['pricing_model'], unique=False)
    op.create_index(op.f('agent_pricing_tenant_id_idx'), 'agent_pricing', ['tenant_id'], unique=False)
    op.create_table('agent_ratings',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent being rated'),
    sa.Column('user_id', sa.String(length=255), nullable=False, comment='User identifier (email, session ID, or UUID)'),
    sa.Column('rating', sa.String(length=20), nullable=False, comment="Rating type: 'like' or 'dislike'"),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_ratings_agent_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_ratings_pkey')),
    sa.UniqueConstraint('agent_id', 'user_id', name='uq_agent_user_rating')
    )
    op.create_index(op.f('agent_ratings_agent_id_idx'), 'agent_ratings', ['agent_id'], unique=False)
    op.create_index(op.f('agent_ratings_user_id_idx'), 'agent_ratings', ['user_id'], unique=False)
    op.create_table('agent_widgets',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent this widget exposes'),
    sa.Column('widget_name', sa.String(length=255), nullable=False, comment='Human-readable name for the widget'),
    sa.Column('api_key', sa.String(length=255), nullable=False, comment='Unique API key for widget authentication'),
    sa.Column('allowed_domains', postgresql.ARRAY(sa.Text()), nullable=True, comment='List of domains allowed to embed this widget (null = all domains)'),
    sa.Column('theme_config', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='JSON configuration for widget appearance and behavior'),
    sa.Column('rate_limit', sa.Integer(), nullable=False, comment='Maximum requests per hour'),
    sa.Column('is_active', sa.Boolean(), nullable=False, comment='Whether the widget is currently active'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_widgets_agent_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_widgets_pkey'))
    )
    op.create_index(op.f('agent_widgets_agent_id_idx'), 'agent_widgets', ['agent_id'], unique=False)
    op.create_index(op.f('agent_widgets_api_key_idx'), 'agent_widgets', ['api_key'], unique=True)
    op.create_index(op.f('agent_widgets_is_active_idx'), 'agent_widgets', ['is_active'], unique=False)
    op.create_index(op.f('agent_widgets_tenant_id_idx'), 'agent_widgets', ['tenant_id'], unique=False)
    op.create_table('credit_balances',
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID'),
    sa.Column('total_credits', sa.Integer(), nullable=False, comment='Total credits allocated'),
    sa.Column('used_credits', sa.Integer(), nullable=False, comment='Credits consumed'),
    sa.Column('available_credits', sa.Integer(), nullable=False, comment='Available credits (computed field)'),
    sa.Column('last_reset_at', sa.DateTime(timezone=True), nullable=True, comment='Last time credits were reset'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('credit_balances_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('credit_balances_pkey'))
    )
    op.create_index(op.f('credit_balances_tenant_id_idx'), 'credit_balances', ['tenant_id'], unique=True)
    op.create_table('credit_topups',
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID'),
    sa.Column('credits', sa.Integer(), nullable=False, comment='Number of credits purchased'),
    sa.Column('amount', sa.Numeric(precision=10, scale=2), nullable=False, comment='Amount paid in currency (e.g., USD)'),
    sa.Column('status', sa.Enum('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED', 'REFUNDED', name='topupstatus'), nullable=False, comment='Payment status'),
    sa.Column('payment_method', sa.String(length=50), nullable=True, comment='Payment method (e.g., stripe, paypal)'),
    sa.Column('stripe_payment_intent_id', sa.String(length=255), nullable=True, comment='Stripe payment intent ID'),
    sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True, comment='When the topup was completed'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('credit_topups_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('credit_topups_pkey'))
    )
    op.create_index(op.f('credit_topups_status_idx'), 'credit_topups', ['status'], unique=False)
    op.create_index(op.f('credit_topups_stripe_payment_intent_id_idx'), 'credit_topups', ['stripe_payment_intent_id'], unique=True)
    op.create_index(op.f('credit_topups_tenant_id_idx'), 'credit_topups', ['tenant_id'], unique=False)
    op.create_table('custom_tools',
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID this tool belongs to'),
    sa.Column('name', sa.String(length=100), nullable=False, comment='Unique name of the custom tool'),
    sa.Column('description', sa.Text(), nullable=True, comment='Description of what the tool does'),
    sa.Column('openapi_schema', postgresql.JSONB(astext_type=sa.Text()), nullable=False, comment='Complete OpenAPI specification'),
    sa.Column('server_url', sa.String(length=500), nullable=False, comment='Base URL for API requests'),
    sa.Column('auth_type', sa.String(length=20), nullable=False, comment='Type of authentication'),
    sa.Column('auth_config', postgresql.JSONB(astext_type=sa.Text()), nullable=False, comment='Encrypted authentication configuration'),
    sa.Column('enabled', sa.Boolean(), nullable=False, comment='Whether the tool is enabled'),
    sa.Column('icon', sa.String(length=200), nullable=True, comment='Optional icon URL or emoji'),
    sa.Column('tags', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Optional tags for categorization'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('custom_tools_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('custom_tools_pkey')),
    sa.UniqueConstraint('tenant_id', 'name', name='uq_custom_tool_name')
    )
    op.create_index(op.f('custom_tools_enabled_idx'), 'custom_tools', ['enabled'], unique=False)
    op.create_index(op.f('custom_tools_name_idx'), 'custom_tools', ['name'], unique=False)
    op.create_index(op.f('custom_tools_tenant_id_idx'), 'custom_tools', ['tenant_id'], unique=False)
    op.create_table('database_connections',
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('description', sa.String(length=1000), nullable=True),
    sa.Column('database_type', sa.String(length=50), nullable=False),
    sa.Column('host', sa.String(length=255), nullable=True),
    sa.Column('port', sa.Integer(), nullable=True),
    sa.Column('database_name', sa.String(length=255), nullable=True),
    sa.Column('username', sa.String(length=255), nullable=True),
    sa.Column('password_encrypted', sa.Text(), nullable=True),
    sa.Column('database_path', sa.String(length=500), nullable=True),
    sa.Column('connection_params', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('status', sa.String(length=50), server_default='active', nullable=False),
    sa.Column('last_tested_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('test_result', sa.Text(), nullable=True),
    sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('database_connections_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('database_connections_pkey'))
    )
    op.create_index(op.f('database_connections_database_type_idx'), 'database_connections', ['database_type'], unique=False)
    op.create_index(op.f('database_connections_status_idx'), 'database_connections', ['status'], unique=False)
    op.create_index(op.f('database_connections_tenant_id_idx'), 'database_connections', ['tenant_id'], unique=False)
    op.create_table('knowledge_bases',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('description', sa.Text(), nullable=True),
    sa.Column('status', sa.Enum('ACTIVE', 'INACTIVE', 'ERROR', 'INITIALIZING', name='knowledgebasestatus'), nullable=False),
    sa.Column('vector_db_provider', sa.Enum('QDRANT', 'PINECONE', 'WEAVIATE', 'CHROMA', 'MILVUS', name='vectordbprovider'), nullable=False),
    sa.Column('vector_db_config', sa.JSON(), nullable=False),
    sa.Column('embedding_provider', sa.Enum('SENTENCE_TRANSFORMERS', 'OPENAI', 'COHERE', 'HUGGINGFACE', 'LITELLM', name='embeddingprovider'), nullable=False),
    sa.Column('embedding_model', sa.String(length=255), nullable=False),
    sa.Column('embedding_config', sa.JSON(), nullable=False),
    sa.Column('is_global', sa.Boolean(), nullable=False),
    sa.Column('chunking_strategy', sa.Enum('FIXED', 'SEMANTIC', 'EMAIL', 'SLACK', 'DOCUMENT', 'CODE', name='chunkingstrategy'), nullable=False),
    sa.Column('chunk_size', sa.Integer(), nullable=False),
    sa.Column('chunk_overlap', sa.Integer(), nullable=False),
    sa.Column('min_chunk_size', sa.Integer(), nullable=False),
    sa.Column('max_chunk_size', sa.Integer(), nullable=False),
    sa.Column('chunking_config', sa.JSON(), nullable=False),
    sa.Column('total_documents', sa.Integer(), nullable=False),
    sa.Column('total_chunks', sa.Integer(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('knowledge_bases_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('knowledge_bases_pkey'))
    )
    op.create_index(op.f('knowledge_bases_tenant_id_idx'), 'knowledge_bases', ['tenant_id'], unique=False)
    op.create_table('oauth_apps',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('provider', sa.String(length=50), nullable=False),
    sa.Column('app_name', sa.String(length=255), nullable=False),
    sa.Column('auth_method', sa.String(length=20), nullable=False),
    sa.Column('client_id', sa.String(length=255), nullable=True),
    sa.Column('client_secret', sa.Text(), nullable=True),
    sa.Column('redirect_uri', sa.String(length=500), nullable=True),
    sa.Column('scopes', sa.JSON(), nullable=True),
    sa.Column('api_token', sa.Text(), 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(), nullable=True),
    sa.Column('is_active', sa.Boolean(), nullable=True),
    sa.Column('is_default', sa.Boolean(), nullable=True),
    sa.Column('tags', sa.JSON(), nullable=True),
    sa.Column('is_internal_tool', sa.Boolean(), nullable=True),
    sa.Column('description', sa.Text(), nullable=True),
    sa.Column('config', sa.JSON(), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('oauth_apps_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('oauth_apps_pkey'))
    )
    op.create_index(op.f('oauth_apps_id_idx'), 'oauth_apps', ['id'], unique=False)
    op.create_index(op.f('oauth_apps_provider_idx'), 'oauth_apps', ['provider'], unique=False)
    op.create_index(op.f('oauth_apps_tenant_id_idx'), 'oauth_apps', ['tenant_id'], unique=False)
    op.create_table('okta_tenants',
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID this Okta config belongs to'),
    sa.Column('domain', sa.String(length=255), nullable=False, comment='Okta domain (e.g., company.okta.com)'),
    sa.Column('client_id', sa.String(length=255), nullable=False, comment='Okta OAuth client ID'),
    sa.Column('client_secret', sa.String(length=500), nullable=False, comment='Encrypted Okta OAuth client secret'),
    sa.Column('issuer_url', sa.String(length=500), nullable=False, comment='Okta issuer URL for token validation'),
    sa.Column('authorization_server_id', sa.String(length=255), nullable=True, comment='Okta authorization server ID (optional, for custom auth servers)'),
    sa.Column('saml_metadata_url', sa.String(length=500), nullable=True, comment='SAML metadata URL'),
    sa.Column('saml_entity_id', sa.String(length=500), nullable=True, comment='SAML entity ID'),
    sa.Column('jit_provisioning_enabled', sa.String(length=10), nullable=False, comment='Enable just-in-time user provisioning'),
    sa.Column('role_mapping', sa.String(length=2000), nullable=True, comment='JSON mapping of Okta groups to Synkora roles'),
    sa.Column('enabled', sa.String(length=10), nullable=False, comment='Whether this Okta tenant is enabled'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('okta_tenants_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('okta_tenants_pkey'))
    )
    op.create_index(op.f('okta_tenants_tenant_id_idx'), 'okta_tenants', ['tenant_id'], unique=False)
    op.create_table('roles',
    sa.Column('tenant_id', sa.UUID(), nullable=True, comment='Tenant ID this role belongs to (NULL for system roles)'),
    sa.Column('name', sa.String(length=100), nullable=False, comment="Role name (e.g., 'Admin', 'Editor', 'Viewer')"),
    sa.Column('description', sa.Text(), nullable=True, comment='Human-readable description of the role'),
    sa.Column('is_system', sa.Boolean(), nullable=False, comment='Whether this is a system role (cannot be deleted)'),
    sa.Column('is_default', sa.Boolean(), nullable=False, comment='Whether this is the default role for new users'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('roles_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('roles_pkey'))
    )
    op.create_index(op.f('roles_name_idx'), 'roles', ['name'], unique=False)
    op.create_index(op.f('roles_tenant_id_idx'), 'roles', ['tenant_id'], unique=False)
    op.create_table('scheduled_tasks',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('description', sa.Text(), nullable=True),
    sa.Column('task_type', sa.String(length=50), nullable=False),
    sa.Column('schedule_type', sa.String(length=20), nullable=False),
    sa.Column('cron_expression', sa.String(length=100), nullable=True),
    sa.Column('interval_seconds', sa.Integer(), nullable=True),
    sa.Column('config', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
    sa.Column('is_active', sa.Boolean(), nullable=False),
    sa.Column('last_run_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('next_run_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.Column('created_by', sa.UUID(), nullable=False),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('scheduled_tasks_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('scheduled_tasks_pkey'))
    )
    op.create_index(op.f('scheduled_tasks_is_active_idx'), 'scheduled_tasks', ['is_active'], unique=False)
    op.create_index(op.f('scheduled_tasks_next_run_at_idx'), 'scheduled_tasks', ['next_run_at'], unique=False)
    op.create_index(op.f('scheduled_tasks_task_type_idx'), 'scheduled_tasks', ['task_type'], unique=False)
    op.create_index(op.f('scheduled_tasks_tenant_id_idx'), 'scheduled_tasks', ['tenant_id'], unique=False)
    op.create_table('slack_bots',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('agent_id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('bot_name', sa.String(length=255), nullable=False),
    sa.Column('slack_app_id', sa.String(length=255), nullable=False),
    sa.Column('slack_bot_token', sa.Text(), nullable=False, comment='Encrypted bot token (xoxb-*)'),
    sa.Column('slack_app_token', sa.Text(), nullable=False, comment='Encrypted app-level token for Socket Mode (xapp-*)'),
    sa.Column('slack_workspace_id', sa.String(length=255), nullable=True),
    sa.Column('slack_workspace_name', sa.String(length=255), nullable=True),
    sa.Column('is_active', sa.Boolean(), nullable=False),
    sa.Column('connection_status', sa.String(length=50), nullable=False, comment='connected, disconnected, error'),
    sa.Column('last_connected_at', sa.DateTime(), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.Column('deleted_at', sa.DateTime(), nullable=True),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('slack_bots_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('slack_bots_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('slack_bots_pkey'))
    )
    op.create_table('social_auth_providers',
    sa.Column('tenant_id', sa.UUID(), nullable=True, comment='Tenant ID (null for global/platform-wide config)'),
    sa.Column('provider_name', sa.String(length=50), nullable=False, comment='Provider name: google, github, microsoft, apple'),
    sa.Column('client_id', sa.String(length=500), nullable=False, comment='OAuth client ID'),
    sa.Column('client_secret', sa.String(length=1000), nullable=False, comment='Encrypted OAuth client secret'),
    sa.Column('redirect_uri', sa.String(length=500), nullable=False, comment='OAuth redirect URI'),
    sa.Column('config', sa.String(length=2000), nullable=True, comment='JSON string of additional provider-specific configuration'),
    sa.Column('enabled', sa.String(length=10), nullable=False, comment='Whether this provider is enabled'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('social_auth_providers_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('social_auth_providers_pkey')),
    sa.UniqueConstraint('tenant_id', 'provider_name', name='uq_tenant_provider')
    )
    op.create_index(op.f('social_auth_providers_tenant_id_idx'), 'social_auth_providers', ['tenant_id'], unique=False)
    op.create_table('teams_bots',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('agent_id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('bot_name', sa.String(length=255), nullable=False),
    sa.Column('app_id', sa.String(length=255), nullable=False, comment='Microsoft App ID'),
    sa.Column('app_password', sa.Text(), nullable=False, comment='Encrypted Microsoft App Password'),
    sa.Column('teams_tenant_id', sa.String(length=255), nullable=True, comment='Microsoft Teams Tenant ID (optional for multi-tenant)'),
    sa.Column('webhook_url', sa.String(length=500), nullable=True, comment='Bot Framework messaging endpoint'),
    sa.Column('is_active', sa.Boolean(), nullable=False),
    sa.Column('connection_status', sa.String(length=50), nullable=False, comment='connected, disconnected, error'),
    sa.Column('last_message_at', sa.DateTime(), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.Column('deleted_at', sa.DateTime(), nullable=True),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('teams_bots_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('teams_bots_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('teams_bots_pkey'))
    )
    op.create_table('tenant_subscriptions',
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID'),
    sa.Column('plan_id', sa.UUID(), nullable=False, comment='Subscription plan ID'),
    sa.Column('status', sa.Enum('ACTIVE', 'CANCELLED', 'EXPIRED', 'SUSPENDED', 'TRIAL', name='subscriptionstatus'), nullable=False, comment='Current subscription status'),
    sa.Column('billing_cycle', sa.Enum('MONTHLY', 'YEARLY', name='billingcycle'), nullable=False, comment='Billing cycle (monthly or yearly)'),
    sa.Column('current_period_start', sa.DateTime(timezone=True), nullable=False, comment='Start date of current billing period'),
    sa.Column('current_period_end', sa.DateTime(timezone=True), nullable=False, comment='End date of current billing period'),
    sa.Column('trial_end', sa.DateTime(timezone=True), nullable=True, comment='Trial period end date (if applicable)'),
    sa.Column('cancelled_at', sa.DateTime(timezone=True), nullable=True, comment='Timestamp when subscription was cancelled'),
    sa.Column('stripe_subscription_id', sa.String(length=255), nullable=True, comment='Stripe subscription ID for payment tracking'),
    sa.Column('stripe_customer_id', sa.String(length=255), nullable=True, comment='Stripe customer ID'),
    sa.Column('auto_renew', sa.String(length=10), nullable=False, comment='Whether subscription auto-renews (stored as string)'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['plan_id'], ['subscription_plans.id'], name=op.f('tenant_subscriptions_plan_id_fkey'), ondelete='RESTRICT'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('tenant_subscriptions_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('tenant_subscriptions_pkey'))
    )
    op.create_index(op.f('tenant_subscriptions_plan_id_idx'), 'tenant_subscriptions', ['plan_id'], unique=False)
    op.create_index(op.f('tenant_subscriptions_status_idx'), 'tenant_subscriptions', ['status'], unique=False)
    op.create_index(op.f('tenant_subscriptions_stripe_customer_id_idx'), 'tenant_subscriptions', ['stripe_customer_id'], unique=False)
    op.create_index(op.f('tenant_subscriptions_stripe_subscription_id_idx'), 'tenant_subscriptions', ['stripe_subscription_id'], unique=True)
    op.create_index(op.f('tenant_subscriptions_tenant_id_idx'), 'tenant_subscriptions', ['tenant_id'], unique=False)
    op.create_table('usage_analytics',
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID'),
    sa.Column('agent_id', sa.UUID(), nullable=True, comment='Agent ID (null for tenant-wide metrics)'),
    sa.Column('date', sa.Date(), nullable=False, comment='Date of the metrics'),
    sa.Column('metric_type', sa.String(length=50), nullable=False, comment='Type of metric (e.g., chat_messages, file_uploads, api_calls)'),
    sa.Column('total_count', sa.Integer(), nullable=False, comment='Total count of the metric'),
    sa.Column('credits_consumed', sa.Integer(), nullable=False, comment='Total credits consumed for this metric'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('usage_analytics_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('usage_analytics_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('usage_analytics_pkey'))
    )
    op.create_index(op.f('usage_analytics_agent_id_idx'), 'usage_analytics', ['agent_id'], unique=False)
    op.create_index(op.f('usage_analytics_date_idx'), 'usage_analytics', ['date'], unique=False)
    op.create_index(op.f('usage_analytics_metric_type_idx'), 'usage_analytics', ['metric_type'], unique=False)
    op.create_index(op.f('usage_analytics_tenant_id_idx'), 'usage_analytics', ['tenant_id'], unique=False)
    op.create_table('whatsapp_bots',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('agent_id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('bot_name', sa.String(length=255), nullable=False),
    sa.Column('phone_number_id', sa.String(length=255), nullable=False, comment='WhatsApp Business phone number ID'),
    sa.Column('whatsapp_business_account_id', sa.String(length=255), nullable=False, comment='WhatsApp Business Account ID'),
    sa.Column('access_token', sa.Text(), nullable=False, comment='Encrypted WhatsApp Business API access token'),
    sa.Column('verify_token', sa.String(length=255), nullable=False, comment='Webhook verification token'),
    sa.Column('webhook_url', sa.String(length=500), nullable=True, comment='Webhook callback URL'),
    sa.Column('is_active', sa.Boolean(), nullable=False),
    sa.Column('connection_status', sa.String(length=50), nullable=False, comment='connected, disconnected, error'),
    sa.Column('last_message_at', sa.DateTime(), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.Column('deleted_at', sa.DateTime(), nullable=True),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('whatsapp_bots_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('whatsapp_bots_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('whatsapp_bots_pkey'))
    )
    op.create_table('writing_style_profiles',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('person_identifier', sa.String(), nullable=False),
    sa.Column('person_name', sa.String(), nullable=True),
    sa.Column('person_role', sa.String(), nullable=True),
    sa.Column('tone_characteristics', sa.JSON(), nullable=True),
    sa.Column('vocabulary_patterns', sa.JSON(), nullable=True),
    sa.Column('sentence_metrics', sa.JSON(), nullable=True),
    sa.Column('communication_patterns', sa.JSON(), nullable=True),
    sa.Column('sample_count', sa.Integer(), nullable=True),
    sa.Column('confidence_score', sa.Float(), nullable=True),
    sa.Column('last_analyzed_at', sa.DateTime(), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=True),
    sa.Column('updated_at', sa.DateTime(), nullable=True),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('writing_style_profiles_tenant_id_fkey')),
    sa.PrimaryKeyConstraint('id', name=op.f('writing_style_profiles_pkey'))
    )
    op.create_index('idx_writing_style_person', 'writing_style_profiles', ['person_identifier'], unique=False)
    op.create_index('idx_writing_style_tenant', 'writing_style_profiles', ['tenant_id'], unique=False)
    op.create_index('idx_writing_style_tenant_person', 'writing_style_profiles', ['tenant_id', 'person_identifier'], unique=False)
    op.create_table('accounts',
    sa.Column('name', sa.String(length=255), nullable=False, comment='User full name'),
    sa.Column('email', sa.String(length=255), nullable=False, comment='User email address'),
    sa.Column('password_hash', sa.String(length=255), nullable=True, comment='Hashed password (null for OAuth users)'),
    sa.Column('interface_language', sa.String(length=10), nullable=False, comment='Preferred UI language'),
    sa.Column('timezone', sa.String(length=50), nullable=False, comment='User timezone'),
    sa.Column('avatar_url', sa.String(length=500), nullable=True, comment='Profile avatar URL'),
    sa.Column('phone', sa.String(length=20), nullable=True, comment='Phone number'),
    sa.Column('bio', sa.String(length=500), nullable=True, comment='User biography/description'),
    sa.Column('company', sa.String(length=255), nullable=True, comment='Company name'),
    sa.Column('job_title', sa.String(length=100), nullable=True, comment='Job title/position'),
    sa.Column('location', sa.String(length=100), nullable=True, comment='Location (city, country)'),
    sa.Column('website', sa.String(length=255), nullable=True, comment='Personal or company website'),
    sa.Column('two_factor_enabled', sa.String(length=10), nullable=False, comment='Whether 2FA is enabled (stored as string for compatibility)'),
    sa.Column('two_factor_secret', sa.String(length=255), nullable=True, comment='2FA secret key (encrypted)'),
    sa.Column('last_login_at', sa.String(length=50), nullable=True, comment='Last login timestamp'),
    sa.Column('last_login_ip', sa.String(length=45), nullable=True, comment='Last login IP address (supports IPv6)'),
    sa.Column('auth_provider', sa.String(length=20), nullable=False, comment='Authentication provider: local, google, github, microsoft, apple, okta'),
    sa.Column('provider_user_id', sa.String(length=255), nullable=True, comment='User ID from OAuth provider'),
    sa.Column('provider_metadata', sa.String(length=2000), nullable=True, comment='JSON string of additional provider data'),
    sa.Column('okta_tenant_id', sa.UUID(), nullable=True, comment='Okta tenant ID for SSO users'),
    sa.Column('is_platform_admin', sa.String(length=10), nullable=False, comment='Whether user is a platform administrator (stored as string)'),
    sa.Column('notification_preferences', sa.String(length=1000), nullable=True, comment='JSON string of notification preferences'),
    sa.Column('reset_token', sa.String(length=255), nullable=True, comment='Password reset token'),
    sa.Column('reset_token_expires_at', sa.String(length=50), nullable=True, comment='Password reset token expiration timestamp'),
    sa.Column('email_verification_token', sa.String(length=255), nullable=True, comment='Email verification token'),
    sa.Column('email_verification_sent_at', sa.String(length=50), nullable=True, comment='Email verification sent timestamp'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('status', sa.String(length=50), nullable=False, comment='Record status'),
    sa.ForeignKeyConstraint(['okta_tenant_id'], ['okta_tenants.id'], name=op.f('accounts_okta_tenant_id_fkey'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('accounts_pkey'))
    )
    op.create_index(op.f('accounts_email_idx'), 'accounts', ['email'], unique=True)
    op.create_index(op.f('accounts_email_verification_token_idx'), 'accounts', ['email_verification_token'], unique=False)
    op.create_index(op.f('accounts_reset_token_idx'), 'accounts', ['reset_token'], unique=False)
    op.create_index(op.f('accounts_status_idx'), 'accounts', ['status'], unique=False)
    op.create_table('agent_api_usages',
    sa.Column('api_key_id', sa.UUID(), nullable=False, comment='API key that was used'),
    sa.Column('endpoint', sa.String(length=255), nullable=False, comment='API endpoint that was called (e.g., /api/v1/agents/{id}/chat)'),
    sa.Column('method', sa.String(length=10), nullable=False, comment='HTTP method (GET, POST, PUT, DELETE, etc.)'),
    sa.Column('status_code', sa.Integer(), nullable=False, comment='HTTP response status code'),
    sa.Column('response_time_ms', sa.Integer(), nullable=True, comment='Response time in milliseconds'),
    sa.Column('tokens_used', sa.Integer(), nullable=True, comment='Number of tokens consumed (for LLM calls)'),
    sa.Column('ip_address', sa.String(length=45), nullable=True, comment='IP address of the requester'),
    sa.Column('user_agent', sa.Text(), nullable=True, comment='User agent string of the requester'),
    sa.Column('request_metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Additional request metadata (headers, query params, body size, etc.)'),
    sa.Column('error_message', sa.Text(), nullable=True, comment='Error message if request failed'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['api_key_id'], ['agent_api_keys.id'], name=op.f('agent_api_usages_api_key_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_api_usages_pkey'))
    )
    op.create_index(op.f('agent_api_usages_api_key_id_idx'), 'agent_api_usages', ['api_key_id'], unique=False)
    op.create_index(op.f('agent_api_usages_endpoint_idx'), 'agent_api_usages', ['endpoint'], unique=False)
    op.create_index(op.f('agent_api_usages_ip_address_idx'), 'agent_api_usages', ['ip_address'], unique=False)
    op.create_index(op.f('agent_api_usages_status_code_idx'), 'agent_api_usages', ['status_code'], unique=False)
    op.create_table('agent_knowledge_bases',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('agent_id', sa.UUID(), nullable=False),
    sa.Column('knowledge_base_id', sa.Integer(), nullable=False),
    sa.Column('retrieval_config', sa.JSON(), nullable=True),
    sa.Column('is_active', sa.Boolean(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_knowledge_bases_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['knowledge_base_id'], ['knowledge_bases.id'], name=op.f('agent_knowledge_bases_knowledge_base_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_knowledge_bases_pkey'))
    )
    op.create_index(op.f('agent_knowledge_bases_agent_id_idx'), 'agent_knowledge_bases', ['agent_id'], unique=False)
    op.create_index(op.f('agent_knowledge_bases_knowledge_base_id_idx'), 'agent_knowledge_bases', ['knowledge_base_id'], unique=False)
    op.create_table('agent_tools',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent ID this tool belongs to'),
    sa.Column('tool_name', sa.String(length=100), nullable=False, comment='Name of the tool'),
    sa.Column('config', postgresql.JSONB(astext_type=sa.Text()), nullable=False, comment='Tool configuration (API keys, settings, etc.)'),
    sa.Column('enabled', sa.Boolean(), nullable=False, comment='Whether the tool is enabled'),
    sa.Column('oauth_app_id', sa.Integer(), nullable=True, comment='Optional: Specific OAuth app to use for this tool'),
    sa.Column('custom_tool_id', sa.UUID(), nullable=True, comment='Optional: Custom tool (OpenAPI) this agent tool references'),
    sa.Column('operation_id', sa.String(length=100), nullable=True, comment="Optional: Specific operation ID from custom tool's OpenAPI schema"),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_tools_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['custom_tool_id'], ['custom_tools.id'], name=op.f('agent_tools_custom_tool_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['oauth_app_id'], ['oauth_apps.id'], name=op.f('agent_tools_oauth_app_id_fkey'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_tools_pkey')),
    sa.UniqueConstraint('agent_id', 'tool_name', name='uq_agent_tool')
    )
    op.create_index(op.f('agent_tools_agent_id_idx'), 'agent_tools', ['agent_id'], unique=False)
    op.create_index(op.f('agent_tools_custom_tool_id_idx'), 'agent_tools', ['custom_tool_id'], unique=False)
    op.create_index(op.f('agent_tools_enabled_idx'), 'agent_tools', ['enabled'], unique=False)
    op.create_index(op.f('agent_tools_oauth_app_id_idx'), 'agent_tools', ['oauth_app_id'], unique=False)
    op.create_index(op.f('agent_tools_tool_name_idx'), 'agent_tools', ['tool_name'], unique=False)
    op.create_table('app_store_sources',
    sa.Column('knowledge_base_id', sa.Integer(), nullable=True, comment='Optional knowledge base for storing reviews'),
    sa.Column('store_type', sa.String(length=50), nullable=False, comment='google_play or apple_app_store'),
    sa.Column('app_id', sa.String(length=255), nullable=False, comment='Package name (Google Play) or App ID (Apple)'),
    sa.Column('app_name', sa.String(length=255), nullable=False, comment='Human-readable app name'),
    sa.Column('sync_frequency', sa.String(length=50), nullable=False, comment='Sync frequency: daily, weekly, or monthly'),
    sa.Column('last_sync_at', sa.DateTime(timezone=True), nullable=True, comment='Last successful sync timestamp'),
    sa.Column('next_sync_at', sa.DateTime(timezone=True), nullable=True, comment='Next scheduled sync timestamp'),
    sa.Column('min_rating', sa.Integer(), nullable=True, comment='Filter reviews by minimum rating (1-5)'),
    sa.Column('languages', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment="List of languages to collect (e.g., ['en', 'es'])"),
    sa.Column('countries', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment="List of countries to collect from (e.g., ['US', 'GB'])"),
    sa.Column('status', sa.String(length=50), nullable=False, comment='Source status: active, paused, or error'),
    sa.Column('total_reviews_collected', sa.Integer(), nullable=False, comment='Total number of reviews collected'),
    sa.Column('config', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Additional configuration options'),
    sa.Column('last_error', sa.Text(), nullable=True, comment='Last error message if any'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.ForeignKeyConstraint(['knowledge_base_id'], ['knowledge_bases.id'], name=op.f('app_store_sources_knowledge_base_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('app_store_sources_pkey'))
    )
    op.create_index(op.f('app_store_sources_tenant_id_idx'), 'app_store_sources', ['tenant_id'], unique=False)
    op.create_table('credit_transactions',
    sa.Column('credit_balance_id', sa.UUID(), nullable=False, comment='Credit balance ID'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID'),
    sa.Column('transaction_type', sa.Enum('PURCHASE', 'SUBSCRIPTION_ALLOCATION', 'USAGE', 'REFUND', 'ADJUSTMENT', 'AGENT_REVENUE', name='transactiontype'), nullable=False, comment='Type of transaction'),
    sa.Column('amount', sa.Integer(), nullable=False, comment='Credit amount (positive for additions, negative for deductions)'),
    sa.Column('balance_after', sa.Integer(), nullable=False, comment='Balance after this transaction'),
    sa.Column('description', sa.Text(), nullable=True, comment='Transaction description'),
    sa.Column('reference_id', sa.UUID(), nullable=True, comment='Reference to related entity (subscription, topup, etc.)'),
    sa.Column('reference_type', sa.String(length=50), nullable=True, comment='Type of referenced entity'),
    sa.Column('transaction_metadata', sa.Text(), nullable=True, comment='Additional transaction metadata (JSON string)'),
    sa.Column('idempotency_key', sa.String(length=255), nullable=True, comment='Idempotency key to prevent duplicate transactions'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['credit_balance_id'], ['credit_balances.id'], name=op.f('credit_transactions_credit_balance_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('credit_transactions_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('credit_transactions_pkey'))
    )
    op.create_index(op.f('credit_transactions_credit_balance_id_idx'), 'credit_transactions', ['credit_balance_id'], unique=False)
    op.create_index(op.f('credit_transactions_idempotency_key_idx'), 'credit_transactions', ['idempotency_key'], unique=True)
    op.create_index(op.f('credit_transactions_reference_id_idx'), 'credit_transactions', ['reference_id'], unique=False)
    op.create_index(op.f('credit_transactions_tenant_id_idx'), 'credit_transactions', ['tenant_id'], unique=False)
    op.create_index(op.f('credit_transactions_transaction_type_idx'), 'credit_transactions', ['transaction_type'], unique=False)
    op.create_table('data_sources',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('knowledge_base_id', sa.Integer(), nullable=True),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('type', sa.Enum('SLACK', 'GMAIL', 'GOOGLE_DRIVE', 'NOTION', 'CONFLUENCE', 'GITHUB', 'MANUAL', 'CUSTOM', name='datasourcetype'), nullable=False),
    sa.Column('status', sa.Enum('ACTIVE', 'INACTIVE', 'ERROR', 'SYNCING', name='datasourcestatus'), nullable=False),
    sa.Column('oauth_app_id', sa.Integer(), nullable=True),
    sa.Column('access_token_encrypted', sa.Text(), nullable=True),
    sa.Column('refresh_token_encrypted', sa.Text(), nullable=True),
    sa.Column('token_expires_at', sa.DateTime(), nullable=True),
    sa.Column('config', sa.JSON(), nullable=False),
    sa.Column('sync_enabled', sa.Boolean(), nullable=False),
    sa.Column('sync_frequency_minutes', sa.Integer(), nullable=False),
    sa.Column('last_sync_at', sa.DateTime(), nullable=True),
    sa.Column('next_sync_at', sa.DateTime(), nullable=True),
    sa.Column('total_documents', sa.Integer(), nullable=False),
    sa.Column('last_error', sa.Text(), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['knowledge_base_id'], ['knowledge_bases.id'], name=op.f('data_sources_knowledge_base_id_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['oauth_app_id'], ['oauth_apps.id'], name=op.f('data_sources_oauth_app_id_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('data_sources_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('data_sources_pkey'))
    )
    op.create_index(op.f('data_sources_knowledge_base_id_idx'), 'data_sources', ['knowledge_base_id'], unique=False)
    op.create_index(op.f('data_sources_tenant_id_idx'), 'data_sources', ['tenant_id'], unique=False)
    op.create_index(op.f('data_sources_type_idx'), 'data_sources', ['type'], unique=False)
    op.create_table('database_connection_agents',
    sa.Column('database_connection_id', sa.UUID(), nullable=False),
    sa.Column('agent_id', sa.UUID(), nullable=False),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('database_connection_agents_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['database_connection_id'], ['database_connections.id'], name=op.f('database_connection_agents_database_connection_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('database_connection_id', 'agent_id', name=op.f('database_connection_agents_pkey'))
    )
    op.create_table('ghostwriter_drafts',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('agent_id', sa.UUID(), nullable=True),
    sa.Column('content_type', sa.String(), nullable=False),
    sa.Column('person_identifier', sa.String(), nullable=False),
    sa.Column('topic', sa.Text(), nullable=False),
    sa.Column('purpose', sa.Text(), nullable=True),
    sa.Column('additional_context', sa.Text(), nullable=True),
    sa.Column('tone_adjustment', sa.String(), nullable=True),
    sa.Column('draft_content', sa.Text(), nullable=False),
    sa.Column('style_profile_id', sa.UUID(), nullable=True),
    sa.Column('sources_used', sa.JSON(), nullable=True),
    sa.Column('confidence_score', sa.Float(), nullable=True),
    sa.Column('generation_time_ms', sa.Integer(), nullable=True),
    sa.Column('model_used', sa.String(), nullable=True),
    sa.Column('provider_used', sa.String(), nullable=True),
    sa.Column('user_rating', sa.Integer(), nullable=True),
    sa.Column('user_feedback', sa.Text(), nullable=True),
    sa.Column('was_edited', sa.Boolean(), nullable=True),
    sa.Column('final_version', sa.Text(), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=True),
    sa.Column('updated_at', sa.DateTime(), nullable=True),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('ghostwriter_drafts_agent_id_fkey')),
    sa.ForeignKeyConstraint(['style_profile_id'], ['writing_style_profiles.id'], name=op.f('ghostwriter_drafts_style_profile_id_fkey')),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('ghostwriter_drafts_tenant_id_fkey')),
    sa.PrimaryKeyConstraint('id', name=op.f('ghostwriter_drafts_pkey'))
    )
    op.create_index('idx_ghostwriter_draft_agent', 'ghostwriter_drafts', ['agent_id'], unique=False)
    op.create_index('idx_ghostwriter_draft_created', 'ghostwriter_drafts', ['created_at'], unique=False)
    op.create_index('idx_ghostwriter_draft_person', 'ghostwriter_drafts', ['person_identifier'], unique=False)
    op.create_index('idx_ghostwriter_draft_style_profile', 'ghostwriter_drafts', ['style_profile_id'], unique=False)
    op.create_index('idx_ghostwriter_draft_tenant', 'ghostwriter_drafts', ['tenant_id'], unique=False)
    op.create_table('role_permissions',
    sa.Column('role_id', sa.UUID(), nullable=False, comment='Role ID reference'),
    sa.Column('permission_id', sa.UUID(), nullable=False, comment='Permission ID'),
    sa.Column('tenant_id', sa.UUID(), nullable=True, comment='Tenant ID (NULL for platform-wide permissions)'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['permission_id'], ['permissions.id'], name=op.f('role_permissions_permission_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['role_id'], ['roles.id'], name=op.f('role_permissions_role_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('role_permissions_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('role_permissions_pkey')),
    sa.UniqueConstraint('role_id', 'permission_id', name='uq_role_id_permission')
    )
    op.create_index(op.f('role_permissions_role_id_idx'), 'role_permissions', ['role_id'], unique=False)
    op.create_table('task_executions',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('task_id', sa.UUID(), nullable=False),
    sa.Column('status', sa.String(length=20), nullable=False),
    sa.Column('started_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('result', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('error_message', sa.Text(), nullable=True),
    sa.Column('execution_time_seconds', sa.Float(), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['task_id'], ['scheduled_tasks.id'], name=op.f('task_executions_task_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('task_executions_pkey'))
    )
    op.create_index(op.f('task_executions_started_at_idx'), 'task_executions', ['started_at'], unique=False)
    op.create_index(op.f('task_executions_status_idx'), 'task_executions', ['status'], unique=False)
    op.create_index(op.f('task_executions_task_id_idx'), 'task_executions', ['task_id'], unique=False)
    op.create_table('task_notifications',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('task_id', sa.UUID(), nullable=False),
    sa.Column('notification_type', sa.String(length=20), nullable=False),
    sa.Column('config', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
    sa.Column('is_active', sa.Boolean(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['task_id'], ['scheduled_tasks.id'], name=op.f('task_notifications_task_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('task_notifications_pkey'))
    )
    op.create_index(op.f('task_notifications_notification_type_idx'), 'task_notifications', ['notification_type'], unique=False)
    op.create_index(op.f('task_notifications_task_id_idx'), 'task_notifications', ['task_id'], unique=False)
    op.create_table('widget_analytics',
    sa.Column('widget_id', sa.UUID(), nullable=False, comment='Widget this analytics record belongs to'),
    sa.Column('session_id', sa.String(length=255), nullable=False, comment='Unique session identifier'),
    sa.Column('messages_count', sa.Integer(), nullable=False, comment='Number of messages in this session'),
    sa.Column('domain', sa.String(length=255), nullable=True, comment='Domain where the widget was embedded'),
    sa.Column('user_agent', sa.Text(), nullable=True, comment='Browser user agent string'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['widget_id'], ['agent_widgets.id'], name=op.f('widget_analytics_widget_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('widget_analytics_pkey'))
    )
    op.create_index(op.f('widget_analytics_domain_idx'), 'widget_analytics', ['domain'], unique=False)
    op.create_index(op.f('widget_analytics_session_id_idx'), 'widget_analytics', ['session_id'], unique=False)
    op.create_index(op.f('widget_analytics_widget_id_idx'), 'widget_analytics', ['widget_id'], unique=False)
    op.create_table('account_providers',
    sa.Column('account_id', sa.UUID(), nullable=False, comment='Account ID'),
    sa.Column('provider', sa.String(length=50), nullable=False, comment='Provider name: local, google, github, microsoft, apple, okta'),
    sa.Column('provider_user_id', sa.String(length=255), nullable=False, comment='User ID from the provider'),
    sa.Column('provider_email', sa.String(length=255), nullable=True, comment='Email from the provider (may differ from account email)'),
    sa.Column('provider_metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Additional provider data as JSON'),
    sa.Column('connected_at', sa.String(length=50), nullable=False, comment='Timestamp when provider was connected'),
    sa.Column('last_used_at', sa.String(length=50), nullable=True, comment='Timestamp when this provider was last used for login'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('account_providers_account_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('account_providers_pkey')),
    sa.UniqueConstraint('account_id', 'provider', name='uq_account_provider'),
    sa.UniqueConstraint('provider', 'provider_user_id', name='uq_provider_user')
    )
    op.create_index(op.f('account_providers_account_id_idx'), 'account_providers', ['account_id'], unique=False)
    op.create_table('activity_logs',
    sa.Column('tenant_id', sa.UUID(), nullable=True, comment='Tenant ID (null for platform-level activities)'),
    sa.Column('account_id', sa.UUID(), nullable=True, comment='Account ID of user who performed the action'),
    sa.Column('activity_type', sa.Enum('AUTH', 'ACCOUNT', 'TENANT', 'TEAM', 'AGENT', 'CONVERSATION', 'RESOURCE', 'SECURITY', 'SYSTEM', name='activitytype'), nullable=False, comment='Type of activity'),
    sa.Column('action', sa.String(length=100), nullable=False, comment="Specific action performed (e.g., 'login', 'create_agent', 'update_role')"),
    sa.Column('resource_type', sa.String(length=50), nullable=True, comment="Type of resource affected (e.g., 'agent', 'user', 'tenant')"),
    sa.Column('resource_id', sa.UUID(), nullable=True, comment='ID of the affected resource'),
    sa.Column('description', sa.Text(), nullable=True, comment='Human-readable description of the activity'),
    sa.Column('activity_metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Additional context and details about the activity'),
    sa.Column('ip_address', sa.String(length=45), nullable=True, comment='IP address of the user (supports IPv6)'),
    sa.Column('user_agent', sa.Text(), nullable=True, comment='User agent string from the request'),
    sa.Column('status', sa.String(length=20), nullable=False, comment='Status of the activity (success, failure, pending)'),
    sa.Column('error_message', sa.Text(), nullable=True, comment='Error message if activity failed'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('activity_logs_account_id_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('activity_logs_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('activity_logs_pkey'))
    )
    op.create_index(op.f('activity_logs_account_id_idx'), 'activity_logs', ['account_id'], unique=False)
    op.create_index(op.f('activity_logs_action_idx'), 'activity_logs', ['action'], unique=False)
    op.create_index(op.f('activity_logs_activity_type_idx'), 'activity_logs', ['activity_type'], unique=False)
    op.create_index(op.f('activity_logs_resource_id_idx'), 'activity_logs', ['resource_id'], unique=False)
    op.create_index(op.f('activity_logs_resource_type_idx'), 'activity_logs', ['resource_type'], unique=False)
    op.create_index(op.f('activity_logs_tenant_id_idx'), 'activity_logs', ['tenant_id'], unique=False)
    op.create_table('agent_revenue',
    sa.Column('agent_pricing_id', sa.UUID(), nullable=False, comment='Agent pricing ID'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Creator tenant ID'),
    sa.Column('transaction_id', sa.UUID(), nullable=True, comment='Related credit transaction ID'),
    sa.Column('total_credits', sa.Integer(), nullable=False, comment='Total credits from transaction'),
    sa.Column('creator_credits', sa.Integer(), nullable=False, comment='Credits allocated to creator'),
    sa.Column('platform_credits', sa.Integer(), nullable=False, comment='Credits allocated to platform'),
    sa.Column('revenue_share_percentage', sa.Numeric(precision=5, scale=2), nullable=False, comment='Revenue share percentage at time of transaction'),
    sa.Column('status', sa.Enum('PENDING', 'PROCESSING', 'PAID', 'FAILED', name='revenuestatus'), nullable=False, comment='Payment status'),
    sa.Column('payout_reference', sa.String(length=255), nullable=True, comment='External payout reference (e.g., Stripe transfer ID)'),
    sa.Column('notes', sa.Text(), nullable=True, comment='Additional notes'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['agent_pricing_id'], ['agent_pricing.id'], name=op.f('agent_revenue_agent_pricing_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('agent_revenue_tenant_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['transaction_id'], ['credit_transactions.id'], name=op.f('agent_revenue_transaction_id_fkey'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_revenue_pkey'))
    )
    op.create_index(op.f('agent_revenue_agent_pricing_id_idx'), 'agent_revenue', ['agent_pricing_id'], unique=False)
    op.create_index(op.f('agent_revenue_status_idx'), 'agent_revenue', ['status'], unique=False)
    op.create_index(op.f('agent_revenue_tenant_id_idx'), 'agent_revenue', ['tenant_id'], unique=False)
    op.create_index(op.f('agent_revenue_transaction_id_idx'), 'agent_revenue', ['transaction_id'], unique=False)
    op.create_table('agent_users',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent ID'),
    sa.Column('account_id', sa.UUID(), nullable=False, comment='Account ID (references central accounts table)'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID (agent owner)'),
    sa.Column('source', sa.String(length=50), nullable=False, comment='How user discovered/accessed agent (widget, api, slack, public_page, etc.)'),
    sa.Column('source_details', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Source-specific details (widget_id, channel_id, referrer, etc.)'),
    sa.Column('session_count', sa.Integer(), nullable=False, comment='Number of conversation sessions'),
    sa.Column('message_count', sa.Integer(), nullable=False, comment='Total messages sent'),
    sa.Column('user_metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Additional tracking metadata (preferences, tags, etc.)'),
    sa.Column('notes', sa.Text(), nullable=True, comment="Internal notes about this user's interaction with the agent"),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('agent_users_account_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('agent_users_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('agent_users_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('agent_users_pkey'))
    )
    op.create_index(op.f('agent_users_account_id_idx'), 'agent_users', ['account_id'], unique=False)
    op.create_index(op.f('agent_users_agent_id_idx'), 'agent_users', ['agent_id'], unique=False)
    op.create_index(op.f('agent_users_source_idx'), 'agent_users', ['source'], unique=False)
    op.create_index(op.f('agent_users_tenant_id_idx'), 'agent_users', ['tenant_id'], unique=False)
    op.create_table('app_reviews',
    sa.Column('app_store_source_id', sa.UUID(), nullable=False, comment='Reference to app store source'),
    sa.Column('review_id', sa.String(length=255), nullable=False, comment='Store-specific review ID'),
    sa.Column('author_name', sa.String(length=255), nullable=True, comment='Review author name'),
    sa.Column('rating', sa.Integer(), nullable=False, comment='Rating from 1-5 stars'),
    sa.Column('title', sa.Text(), nullable=True, comment='Review title'),
    sa.Column('content', sa.Text(), nullable=False, comment='Review content/text'),
    sa.Column('language', sa.String(length=10), nullable=True, comment="Review language code (e.g., 'en', 'es')"),
    sa.Column('country', sa.String(length=10), nullable=True, comment="Country code (e.g., 'US', 'GB')"),
    sa.Column('app_version', sa.String(length=50), nullable=True, comment='App version being reviewed'),
    sa.Column('device_type', sa.String(length=100), nullable=True, comment="Device type used (e.g., 'iPhone 12', 'Pixel 5')"),
    sa.Column('review_date', sa.DateTime(timezone=True), nullable=False, comment='When the review was posted'),
    sa.Column('thumbs_up_count', sa.Integer(), nullable=True, comment='Number of thumbs up/helpful votes'),
    sa.Column('sentiment', sa.String(length=50), nullable=True, comment='Sentiment: positive, negative, neutral, or mixed'),
    sa.Column('sentiment_score', sa.Float(), nullable=True, comment='Sentiment score from -1 (negative) to 1 (positive)'),
    sa.Column('topics', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment="Extracted topics (e.g., ['ui_ux', 'performance'])"),
    sa.Column('issues', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Identified issues with severity levels'),
    sa.Column('features_mentioned', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment='Features mentioned in the review'),
    sa.Column('has_response', sa.Boolean(), nullable=False, comment='Whether developer responded to review'),
    sa.Column('response_text', sa.Text(), nullable=True, comment='Developer response text'),
    sa.Column('response_date', sa.DateTime(timezone=True), nullable=True, comment='When developer responded'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['app_store_source_id'], ['app_store_sources.id'], name=op.f('app_reviews_app_store_source_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('app_reviews_pkey'))
    )
    op.create_table('apps',
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('created_by', sa.UUID(), nullable=False),
    sa.Column('updated_by', sa.UUID(), nullable=True),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('description', sa.Text(), nullable=True),
    sa.Column('mode', sa.Enum('COMPLETION', 'CHAT', 'AGENT', 'WORKFLOW', name='appmode', native_enum=False, length=50), nullable=False),
    sa.Column('icon', sa.String(length=255), nullable=False),
    sa.Column('icon_background', sa.String(length=50), nullable=False),
    sa.Column('model_config', sa.JSON(), nullable=False),
    sa.Column('app_config', sa.JSON(), nullable=False),
    sa.Column('status', sa.Enum('DRAFT', 'ACTIVE', 'INACTIVE', 'ARCHIVED', name='appstatus', native_enum=False, length=50), nullable=False),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True, comment='Soft delete timestamp'),
    sa.Column('deleted_by', sa.UUID(), nullable=True, comment='User who deleted the record'),
    sa.ForeignKeyConstraint(['created_by'], ['accounts.id'], name=op.f('apps_created_by_fkey')),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('apps_tenant_id_fkey')),
    sa.ForeignKeyConstraint(['updated_by'], ['accounts.id'], name=op.f('apps_updated_by_fkey')),
    sa.PrimaryKeyConstraint('id', name=op.f('apps_pkey'))
    )
    op.create_table('data_source_documents',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('data_source_id', sa.Integer(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('external_id', sa.String(length=255), nullable=False),
    sa.Column('external_url', sa.Text(), nullable=True),
    sa.Column('title', sa.String(length=500), nullable=True),
    sa.Column('content', sa.Text(), nullable=False),
    sa.Column('content_type', sa.String(length=50), nullable=False),
    sa.Column('doc_metadata', sa.JSON(), nullable=False),
    sa.Column('embedding_id', sa.String(length=255), nullable=True),
    sa.Column('is_embedded', sa.Boolean(), nullable=False),
    sa.Column('source_created_at', sa.DateTime(), nullable=True),
    sa.Column('source_updated_at', sa.DateTime(), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['data_source_id'], ['data_sources.id'], name=op.f('data_source_documents_data_source_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('data_source_documents_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('data_source_documents_pkey'))
    )
    op.create_index(op.f('data_source_documents_data_source_id_idx'), 'data_source_documents', ['data_source_id'], unique=False)
    op.create_index(op.f('data_source_documents_embedding_id_idx'), 'data_source_documents', ['embedding_id'], unique=False)
    op.create_index(op.f('data_source_documents_external_id_idx'), 'data_source_documents', ['external_id'], unique=False)
    op.create_index(op.f('data_source_documents_tenant_id_idx'), 'data_source_documents', ['tenant_id'], unique=False)
    op.create_table('data_source_sync_jobs',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('data_source_id', sa.Integer(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('status', sa.Enum('PENDING', 'IN_PROGRESS', 'COMPLETED', 'FAILED', name='syncstatus'), nullable=False),
    sa.Column('started_at', sa.DateTime(), nullable=True),
    sa.Column('completed_at', sa.DateTime(), nullable=True),
    sa.Column('error_message', sa.Text(), nullable=True),
    sa.Column('documents_processed', sa.Integer(), nullable=False),
    sa.Column('documents_added', sa.Integer(), nullable=False),
    sa.Column('documents_updated', sa.Integer(), nullable=False),
    sa.Column('documents_deleted', sa.Integer(), nullable=False),
    sa.Column('documents_failed', sa.Integer(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['data_source_id'], ['data_sources.id'], name=op.f('data_source_sync_jobs_data_source_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('data_source_sync_jobs_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('data_source_sync_jobs_pkey'))
    )
    op.create_index(op.f('data_source_sync_jobs_data_source_id_idx'), 'data_source_sync_jobs', ['data_source_id'], unique=False)
    op.create_index(op.f('data_source_sync_jobs_status_idx'), 'data_source_sync_jobs', ['status'], unique=False)
    op.create_index(op.f('data_source_sync_jobs_tenant_id_idx'), 'data_source_sync_jobs', ['tenant_id'], unique=False)
    op.create_table('datasets',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('description', sa.Text(), nullable=True),
    sa.Column('embedding_model', sa.String(length=100), nullable=False),
    sa.Column('embedding_dimension', sa.Integer(), nullable=False),
    sa.Column('retrieval_model_config', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('created_by', sa.UUID(), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['created_by'], ['accounts.id'], name=op.f('datasets_created_by_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('datasets_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('datasets_pkey'))
    )
    op.create_index(op.f('datasets_created_by_idx'), 'datasets', ['created_by'], unique=False)
    op.create_index(op.f('datasets_tenant_id_idx'), 'datasets', ['tenant_id'], unique=False)
    op.create_table('documents',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('knowledge_base_id', sa.Integer(), nullable=False),
    sa.Column('data_source_id', sa.Integer(), nullable=True),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('content', sa.Text(), nullable=True),
    sa.Column('content_type', sa.String(length=50), nullable=False),
    sa.Column('s3_bucket', sa.String(length=255), nullable=True),
    sa.Column('s3_key', sa.String(length=500), nullable=True),
    sa.Column('s3_url', sa.Text(), nullable=True),
    sa.Column('file_size', sa.Integer(), nullable=True),
    sa.Column('mime_type', sa.String(length=100), nullable=True),
    sa.Column('source_type', sa.String(length=50), nullable=False),
    sa.Column('external_id', sa.String(length=255), nullable=True),
    sa.Column('external_url', sa.Text(), nullable=True),
    sa.Column('doc_metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('has_images', sa.Boolean(), nullable=False),
    sa.Column('image_count', sa.Integer(), nullable=False),
    sa.Column('images', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('original_filename', sa.String(length=255), nullable=True),
    sa.Column('upload_source', sa.String(length=50), nullable=False),
    sa.Column('word_count', sa.Integer(), nullable=True),
    sa.Column('char_count', sa.Integer(), nullable=True),
    sa.Column('status', sa.Enum('PENDING', 'PROCESSING', 'COMPLETED', 'ERROR', 'ARCHIVED', name='documentstatus'), nullable=False),
    sa.Column('error', sa.Text(), nullable=True),
    sa.Column('source_created_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('source_updated_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(['data_source_id'], ['data_sources.id'], name=op.f('documents_data_source_id_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['knowledge_base_id'], ['knowledge_bases.id'], name=op.f('documents_knowledge_base_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('documents_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('documents_pkey'))
    )
    op.create_index(op.f('documents_created_at_idx'), 'documents', ['created_at'], unique=False)
    op.create_index(op.f('documents_data_source_id_idx'), 'documents', ['data_source_id'], unique=False)
    op.create_index(op.f('documents_knowledge_base_id_idx'), 'documents', ['knowledge_base_id'], unique=False)
    op.create_index(op.f('documents_status_idx'), 'documents', ['status'], unique=False)
    op.create_index(op.f('documents_tenant_id_idx'), 'documents', ['tenant_id'], unique=False)
    op.create_table('review_analytics',
    sa.Column('app_store_source_id', sa.UUID(), nullable=False, comment='Reference to app store source'),
    sa.Column('period_type', sa.String(length=50), nullable=False, comment='Period type: daily, weekly, or monthly'),
    sa.Column('period_start', sa.DateTime(timezone=True), nullable=False, comment='Period start timestamp'),
    sa.Column('period_end', sa.DateTime(timezone=True), nullable=False, comment='Period end timestamp'),
    sa.Column('total_reviews', sa.Integer(), nullable=False, comment='Total reviews in this period'),
    sa.Column('average_rating', sa.Float(), nullable=True, comment='Average rating in this period'),
    sa.Column('rating_distribution', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment="Distribution of ratings (e.g., {'1': 5, '2': 10, '3': 20, '4': 30, '5': 35})"),
    sa.Column('sentiment_distribution', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment="Distribution of sentiments (e.g., {'positive': 60, 'negative': 20, 'neutral': 15, 'mixed': 5})"),
    sa.Column('sentiment_trend', sa.String(length=50), nullable=True, comment='Sentiment trend: improving, declining, or stable'),
    sa.Column('top_topics', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment="Most mentioned topics with counts (e.g., [{'topic': 'ui_ux', 'count': 45}, ...])"),
    sa.Column('top_issues', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment="Most reported issues with severity (e.g., [{'issue': 'crash', 'count': 12, 'severity': 'high'}, ...])"),
    sa.Column('top_features', postgresql.JSONB(astext_type=sa.Text()), nullable=True, comment="Most mentioned features (e.g., [{'feature': 'dark_mode', 'count': 30}, ...])"),
    sa.Column('rating_change', sa.Float(), nullable=True, comment='Change in average rating from previous period'),
    sa.Column('review_volume_change', sa.Float(), nullable=True, comment='Percentage change in review volume from previous period'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['app_store_source_id'], ['app_store_sources.id'], name=op.f('review_analytics_app_store_source_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('review_analytics_pkey'))
    )
    op.create_table('team_invitations',
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID'),
    sa.Column('email', sa.String(length=255), nullable=False, comment='Invitee email address'),
    sa.Column('role', sa.String(length=50), nullable=False, comment='Role to assign upon acceptance (OWNER/ADMIN/EDITOR/MEMBER)'),
    sa.Column('invited_by', sa.UUID(), nullable=True, comment='Account ID of inviter'),
    sa.Column('token', sa.String(length=255), nullable=False, comment='Unique invitation token'),
    sa.Column('status', sa.Enum('PENDING', 'ACCEPTED', 'EXPIRED', 'REVOKED', name='invitationstatus'), nullable=False, comment='Invitation status'),
    sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False, comment='Invitation expiry timestamp'),
    sa.Column('accepted_at', sa.DateTime(timezone=True), nullable=True, comment='Timestamp when invitation was accepted'),
    sa.Column('revoked_at', sa.DateTime(timezone=True), nullable=True, comment='Timestamp when invitation was revoked'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['invited_by'], ['accounts.id'], name=op.f('team_invitations_invited_by_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('team_invitations_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('team_invitations_pkey'))
    )
    op.create_index(op.f('team_invitations_email_idx'), 'team_invitations', ['email'], unique=False)
    op.create_index(op.f('team_invitations_status_idx'), 'team_invitations', ['status'], unique=False)
    op.create_index(op.f('team_invitations_tenant_id_idx'), 'team_invitations', ['tenant_id'], unique=False)
    op.create_index(op.f('team_invitations_token_idx'), 'team_invitations', ['token'], unique=True)
    op.create_table('tenant_account_joins',
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant ID'),
    sa.Column('account_id', sa.UUID(), nullable=False, comment='Account ID'),
    sa.Column('role', sa.Enum('OWNER', 'ADMIN', 'EDITOR', 'NORMAL', name='accountrole'), nullable=False, comment='User role in tenant'),
    sa.Column('invited_by', sa.UUID(), nullable=True, comment='Account ID of user who sent the invitation'),
    sa.Column('joined_at', sa.String(length=50), nullable=True, comment='Timestamp when user joined the tenant'),
    sa.Column('custom_permissions', sa.String(length=2000), nullable=True, comment='JSON string of custom permission overrides for this user in this tenant'),
    sa.Column('role_id', sa.UUID(), nullable=True, comment='Role ID for granular permission control'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('tenant_account_joins_account_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['invited_by'], ['accounts.id'], name=op.f('tenant_account_joins_invited_by_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['role_id'], ['roles.id'], name=op.f('tenant_account_joins_role_id_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('tenant_account_joins_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('tenant_account_joins_pkey')),
    sa.UniqueConstraint('tenant_id', 'account_id', name='uq_tenant_account')
    )
    op.create_index(op.f('tenant_account_joins_account_id_idx'), 'tenant_account_joins', ['account_id'], unique=False)
    op.create_index(op.f('tenant_account_joins_role_id_idx'), 'tenant_account_joins', ['role_id'], unique=False)
    op.create_index(op.f('tenant_account_joins_tenant_id_idx'), 'tenant_account_joins', ['tenant_id'], unique=False)
    op.create_table('upload_files',
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('created_by', sa.UUID(), nullable=True),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('key', sa.String(length=500), nullable=False),
    sa.Column('size', sa.Integer(), nullable=False),
    sa.Column('extension', sa.String(length=50), nullable=False),
    sa.Column('mime_type', sa.String(length=255), nullable=False),
    sa.Column('file_type', sa.String(length=50), nullable=False),
    sa.Column('source', sa.String(length=50), nullable=False),
    sa.Column('storage_type', sa.String(length=50), nullable=False),
    sa.Column('hash', sa.String(length=64), nullable=True),
    sa.Column('metadata', sa.Text(), nullable=True),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['created_by'], ['accounts.id'], name=op.f('upload_files_created_by_fkey'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('upload_files_tenant_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('upload_files_pkey')),
    sa.UniqueConstraint('key', name=op.f('upload_files_key_key'))
    )
    op.create_index(op.f('upload_files_created_by_idx'), 'upload_files', ['created_by'], unique=False)
    op.create_index(op.f('upload_files_tenant_id_idx'), 'upload_files', ['tenant_id'], unique=False)
    op.create_table('conversations',
    sa.Column('app_id', sa.UUID(), nullable=True),
    sa.Column('account_id', sa.UUID(), nullable=True),
    sa.Column('agent_id', sa.UUID(), nullable=True),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('summary', sa.Text(), nullable=True),
    sa.Column('session_id', sa.String(length=255), nullable=True),
    sa.Column('status', sa.Enum('ACTIVE', 'ARCHIVED', 'DELETED', name='conversationstatus', native_enum=False, length=50), nullable=False),
    sa.Column('message_count', sa.Integer(), nullable=False),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True, comment='Soft delete timestamp'),
    sa.Column('deleted_by', sa.UUID(), nullable=True, comment='User who deleted the record'),
    sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], name=op.f('conversations_account_id_fkey')),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('conversations_agent_id_fkey')),
    sa.ForeignKeyConstraint(['app_id'], ['apps.id'], name=op.f('conversations_app_id_fkey')),
    sa.PrimaryKeyConstraint('id', name=op.f('conversations_pkey'))
    )
    op.create_table('document_segments',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('dataset_id', sa.UUID(), nullable=True),
    sa.Column('document_id', sa.UUID(), nullable=False),
    sa.Column('position', sa.Integer(), nullable=False),
    sa.Column('content', sa.Text(), nullable=False),
    sa.Column('answer', sa.Text(), nullable=True),
    sa.Column('word_count', sa.Integer(), nullable=False),
    sa.Column('tokens', sa.Integer(), nullable=False),
    sa.Column('keywords', postgresql.JSON(astext_type=sa.Text()), nullable=True),
    sa.Column('index_node_id', sa.String(length=255), nullable=True),
    sa.Column('index_node_hash', sa.String(length=255), nullable=True),
    sa.Column('hit_count', sa.Integer(), nullable=False),
    sa.Column('enabled', sa.Boolean(), nullable=False),
    sa.Column('disabled_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('disabled_by', sa.UUID(), nullable=True),
    sa.Column('status', sa.String(length=255), nullable=False),
    sa.Column('created_by', sa.UUID(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
    sa.Column('updated_by', sa.UUID(), nullable=True),
    sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
    sa.Column('indexing_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('error', sa.Text(), nullable=True),
    sa.Column('stopped_at', sa.DateTime(timezone=True), nullable=True),
    sa.ForeignKeyConstraint(['dataset_id'], ['datasets.id'], name=op.f('document_segments_dataset_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['document_id'], ['documents.id'], name=op.f('document_segments_document_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('document_segments_pkey'))
    )
    op.create_index(op.f('document_segments_created_at_idx'), 'document_segments', ['created_at'], unique=False)
    op.create_index(op.f('document_segments_dataset_id_idx'), 'document_segments', ['dataset_id'], unique=False)
    op.create_index(op.f('document_segments_document_id_idx'), 'document_segments', ['document_id'], unique=False)
    op.create_index(op.f('document_segments_tenant_id_idx'), 'document_segments', ['tenant_id'], unique=False)
    op.create_table('messages',
    sa.Column('conversation_id', sa.UUID(), nullable=False),
    sa.Column('role', sa.Enum('USER', 'ASSISTANT', 'SYSTEM', name='messagerole', native_enum=False, length=50), nullable=False),
    sa.Column('content', sa.Text(), nullable=False),
    sa.Column('message_metadata', sa.JSON(), nullable=False),
    sa.Column('status', sa.Enum('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED', name='messagestatus', native_enum=False, length=50), nullable=False),
    sa.Column('error', sa.Text(), nullable=True),
    sa.Column('token_count', sa.Integer(), nullable=False),
    sa.Column('position', sa.Integer(), nullable=False),
    sa.Column('attachments', sa.JSON(), nullable=True),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], name=op.f('messages_conversation_id_fkey')),
    sa.PrimaryKeyConstraint('id', name=op.f('messages_pkey'))
    )
    op.create_table('slack_conversations',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('slack_bot_id', sa.UUID(), nullable=False),
    sa.Column('conversation_id', sa.UUID(), nullable=False),
    sa.Column('slack_channel_id', sa.String(length=255), nullable=False),
    sa.Column('slack_user_id', sa.String(length=255), nullable=False),
    sa.Column('slack_thread_ts', sa.String(length=255), nullable=True, comment='Thread timestamp for threaded conversations'),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], name=op.f('slack_conversations_conversation_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['slack_bot_id'], ['slack_bots.id'], name=op.f('slack_conversations_slack_bot_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('slack_conversations_pkey'))
    )
    op.create_table('teams_conversations',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('teams_bot_id', sa.UUID(), nullable=False),
    sa.Column('conversation_id', sa.UUID(), nullable=False),
    sa.Column('teams_conversation_id', sa.String(length=255), nullable=False, comment='Teams conversation ID'),
    sa.Column('teams_user_id', sa.String(length=255), nullable=False, comment='Teams user ID (AAD object ID)'),
    sa.Column('teams_user_name', sa.String(length=255), nullable=True),
    sa.Column('teams_channel_id', sa.String(length=255), nullable=True, comment='Channel ID if in a channel'),
    sa.Column('teams_team_id', sa.String(length=255), nullable=True, comment='Team ID if in a team'),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], name=op.f('teams_conversations_conversation_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['teams_bot_id'], ['teams_bots.id'], name=op.f('teams_conversations_teams_bot_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('teams_conversations_pkey'))
    )
    op.create_table('voice_usage',
    sa.Column('agent_id', sa.UUID(), nullable=False, comment='Agent that used the voice service'),
    sa.Column('conversation_id', sa.UUID(), nullable=True, comment='Conversation where voice was used'),
    sa.Column('provider', sa.String(length=50), nullable=False, comment='Voice provider used'),
    sa.Column('operation_type', sa.String(length=20), nullable=False, comment='Operation type (stt, tts)'),
    sa.Column('input_length', sa.Integer(), nullable=True, comment='Input length (audio duration in seconds for STT, text length for TTS)'),
    sa.Column('output_length', sa.Integer(), nullable=True, comment='Output length (text length for STT, audio duration for TTS)'),
    sa.Column('cost', sa.Float(), nullable=True, comment='Estimated cost in USD'),
    sa.Column('id', sa.UUID(), nullable=False, comment='Unique identifier'),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='Record creation timestamp'),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='Record last update timestamp'),
    sa.Column('tenant_id', sa.UUID(), nullable=False, comment='Tenant identifier for multi-tenancy'),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('voice_usage_agent_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], name=op.f('voice_usage_conversation_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('voice_usage_pkey'))
    )
    op.create_index(op.f('voice_usage_tenant_id_idx'), 'voice_usage', ['tenant_id'], unique=False)
    op.create_table('whatsapp_conversations',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('whatsapp_bot_id', sa.UUID(), nullable=False),
    sa.Column('conversation_id', sa.UUID(), nullable=False),
    sa.Column('whatsapp_user_id', sa.String(length=255), nullable=False, comment='WhatsApp user phone number'),
    sa.Column('whatsapp_user_name', sa.String(length=255), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], name=op.f('whatsapp_conversations_conversation_id_fkey'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['whatsapp_bot_id'], ['whatsapp_bots.id'], name=op.f('whatsapp_conversations_whatsapp_bot_id_fkey'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('whatsapp_conversations_pkey'))
    )
    op.create_table('charts',
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('tenant_id', sa.UUID(), nullable=False),
    sa.Column('agent_id', sa.UUID(), nullable=True),
    sa.Column('conversation_id', sa.UUID(), nullable=True),
    sa.Column('message_id', sa.UUID(), nullable=True),
    sa.Column('title', sa.String(length=255), nullable=False),
    sa.Column('description', sa.Text(), nullable=True),
    sa.Column('chart_type', sa.String(length=50), nullable=False),
    sa.Column('library', sa.String(length=50), nullable=False),
    sa.Column('config', sa.JSON(), nullable=False),
    sa.Column('data', sa.JSON(), nullable=False),
    sa.Column('query', sa.Text(), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('charts_agent_id_fkey')),
    sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], name=op.f('charts_conversation_id_fkey')),
    sa.ForeignKeyConstraint(['message_id'], ['messages.id'], name=op.f('charts_message_id_fkey')),
    sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], name=op.f('charts_tenant_id_fkey')),
    sa.PrimaryKeyConstraint('id', name=op.f('charts_pkey'))
    )
    # ### end Alembic commands ###


def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('charts')
    op.drop_table('whatsapp_conversations')
    op.drop_index(op.f('voice_usage_tenant_id_idx'), table_name='voice_usage')
    op.drop_table('voice_usage')
    op.drop_table('teams_conversations')
    op.drop_table('slack_conversations')
    op.drop_table('messages')
    op.drop_index(op.f('document_segments_tenant_id_idx'), table_name='document_segments')
    op.drop_index(op.f('document_segments_document_id_idx'), table_name='document_segments')
    op.drop_index(op.f('document_segments_dataset_id_idx'), table_name='document_segments')
    op.drop_index(op.f('document_segments_created_at_idx'), table_name='document_segments')
    op.drop_table('document_segments')
    op.drop_table('conversations')
    op.drop_index(op.f('upload_files_tenant_id_idx'), table_name='upload_files')
    op.drop_index(op.f('upload_files_created_by_idx'), table_name='upload_files')
    op.drop_table('upload_files')
    op.drop_index(op.f('tenant_account_joins_tenant_id_idx'), table_name='tenant_account_joins')
    op.drop_index(op.f('tenant_account_joins_role_id_idx'), table_name='tenant_account_joins')
    op.drop_index(op.f('tenant_account_joins_account_id_idx'), table_name='tenant_account_joins')
    op.drop_table('tenant_account_joins')
    op.drop_index(op.f('team_invitations_token_idx'), table_name='team_invitations')
    op.drop_index(op.f('team_invitations_tenant_id_idx'), table_name='team_invitations')
    op.drop_index(op.f('team_invitations_status_idx'), table_name='team_invitations')
    op.drop_index(op.f('team_invitations_email_idx'), table_name='team_invitations')
    op.drop_table('team_invitations')
    op.drop_table('review_analytics')
    op.drop_index(op.f('documents_tenant_id_idx'), table_name='documents')
    op.drop_index(op.f('documents_status_idx'), table_name='documents')
    op.drop_index(op.f('documents_knowledge_base_id_idx'), table_name='documents')
    op.drop_index(op.f('documents_data_source_id_idx'), table_name='documents')
    op.drop_index(op.f('documents_created_at_idx'), table_name='documents')
    op.drop_table('documents')
    op.drop_index(op.f('datasets_tenant_id_idx'), table_name='datasets')
    op.drop_index(op.f('datasets_created_by_idx'), table_name='datasets')
    op.drop_table('datasets')
    op.drop_index(op.f('data_source_sync_jobs_tenant_id_idx'), table_name='data_source_sync_jobs')
    op.drop_index(op.f('data_source_sync_jobs_status_idx'), table_name='data_source_sync_jobs')
    op.drop_index(op.f('data_source_sync_jobs_data_source_id_idx'), table_name='data_source_sync_jobs')
    op.drop_table('data_source_sync_jobs')
    op.drop_index(op.f('data_source_documents_tenant_id_idx'), table_name='data_source_documents')
    op.drop_index(op.f('data_source_documents_external_id_idx'), table_name='data_source_documents')
    op.drop_index(op.f('data_source_documents_embedding_id_idx'), table_name='data_source_documents')
    op.drop_index(op.f('data_source_documents_data_source_id_idx'), table_name='data_source_documents')
    op.drop_table('data_source_documents')
    op.drop_table('apps')
    op.drop_table('app_reviews')
    op.drop_index(op.f('agent_users_tenant_id_idx'), table_name='agent_users')
    op.drop_index(op.f('agent_users_source_idx'), table_name='agent_users')
    op.drop_index(op.f('agent_users_agent_id_idx'), table_name='agent_users')
    op.drop_index(op.f('agent_users_account_id_idx'), table_name='agent_users')
    op.drop_table('agent_users')
    op.drop_index(op.f('agent_revenue_transaction_id_idx'), table_name='agent_revenue')
    op.drop_index(op.f('agent_revenue_tenant_id_idx'), table_name='agent_revenue')
    op.drop_index(op.f('agent_revenue_status_idx'), table_name='agent_revenue')
    op.drop_index(op.f('agent_revenue_agent_pricing_id_idx'), table_name='agent_revenue')
    op.drop_table('agent_revenue')
    op.drop_index(op.f('activity_logs_tenant_id_idx'), table_name='activity_logs')
    op.drop_index(op.f('activity_logs_resource_type_idx'), table_name='activity_logs')
    op.drop_index(op.f('activity_logs_resource_id_idx'), table_name='activity_logs')
    op.drop_index(op.f('activity_logs_activity_type_idx'), table_name='activity_logs')
    op.drop_index(op.f('activity_logs_action_idx'), table_name='activity_logs')
    op.drop_index(op.f('activity_logs_account_id_idx'), table_name='activity_logs')
    op.drop_table('activity_logs')
    op.drop_index(op.f('account_providers_account_id_idx'), table_name='account_providers')
    op.drop_table('account_providers')
    op.drop_index(op.f('widget_analytics_widget_id_idx'), table_name='widget_analytics')
    op.drop_index(op.f('widget_analytics_session_id_idx'), table_name='widget_analytics')
    op.drop_index(op.f('widget_analytics_domain_idx'), table_name='widget_analytics')
    op.drop_table('widget_analytics')
    op.drop_index(op.f('task_notifications_task_id_idx'), table_name='task_notifications')
    op.drop_index(op.f('task_notifications_notification_type_idx'), table_name='task_notifications')
    op.drop_table('task_notifications')
    op.drop_index(op.f('task_executions_task_id_idx'), table_name='task_executions')
    op.drop_index(op.f('task_executions_status_idx'), table_name='task_executions')
    op.drop_index(op.f('task_executions_started_at_idx'), table_name='task_executions')
    op.drop_table('task_executions')
    op.drop_index(op.f('role_permissions_role_id_idx'), table_name='role_permissions')
    op.drop_table('role_permissions')
    op.drop_index('idx_ghostwriter_draft_tenant', table_name='ghostwriter_drafts')
    op.drop_index('idx_ghostwriter_draft_style_profile', table_name='ghostwriter_drafts')
    op.drop_index('idx_ghostwriter_draft_person', table_name='ghostwriter_drafts')
    op.drop_index('idx_ghostwriter_draft_created', table_name='ghostwriter_drafts')
    op.drop_index('idx_ghostwriter_draft_agent', table_name='ghostwriter_drafts')
    op.drop_table('ghostwriter_drafts')
    op.drop_table('database_connection_agents')
    op.drop_index(op.f('data_sources_type_idx'), table_name='data_sources')
    op.drop_index(op.f('data_sources_tenant_id_idx'), table_name='data_sources')
    op.drop_index(op.f('data_sources_knowledge_base_id_idx'), table_name='data_sources')
    op.drop_table('data_sources')
    op.drop_index(op.f('credit_transactions_transaction_type_idx'), table_name='credit_transactions')
    op.drop_index(op.f('credit_transactions_tenant_id_idx'), table_name='credit_transactions')
    op.drop_index(op.f('credit_transactions_reference_id_idx'), table_name='credit_transactions')
    op.drop_index(op.f('credit_transactions_idempotency_key_idx'), table_name='credit_transactions')
    op.drop_index(op.f('credit_transactions_credit_balance_id_idx'), table_name='credit_transactions')
    op.drop_table('credit_transactions')
    op.drop_index(op.f('app_store_sources_tenant_id_idx'), table_name='app_store_sources')
    op.drop_table('app_store_sources')
    op.drop_index(op.f('agent_tools_tool_name_idx'), table_name='agent_tools')
    op.drop_index(op.f('agent_tools_oauth_app_id_idx'), table_name='agent_tools')
    op.drop_index(op.f('agent_tools_enabled_idx'), table_name='agent_tools')
    op.drop_index(op.f('agent_tools_custom_tool_id_idx'), table_name='agent_tools')
    op.drop_index(op.f('agent_tools_agent_id_idx'), table_name='agent_tools')
    op.drop_table('agent_tools')
    op.drop_index(op.f('agent_knowledge_bases_knowledge_base_id_idx'), table_name='agent_knowledge_bases')
    op.drop_index(op.f('agent_knowledge_bases_agent_id_idx'), table_name='agent_knowledge_bases')
    op.drop_table('agent_knowledge_bases')
    op.drop_index(op.f('agent_api_usages_status_code_idx'), table_name='agent_api_usages')
    op.drop_index(op.f('agent_api_usages_ip_address_idx'), table_name='agent_api_usages')
    op.drop_index(op.f('agent_api_usages_endpoint_idx'), table_name='agent_api_usages')
    op.drop_index(op.f('agent_api_usages_api_key_id_idx'), table_name='agent_api_usages')
    op.drop_table('agent_api_usages')
    op.drop_index(op.f('accounts_status_idx'), table_name='accounts')
    op.drop_index(op.f('accounts_reset_token_idx'), table_name='accounts')
    op.drop_index(op.f('accounts_email_verification_token_idx'), table_name='accounts')
    op.drop_index(op.f('accounts_email_idx'), table_name='accounts')
    op.drop_table('accounts')
    op.drop_index('idx_writing_style_tenant_person', table_name='writing_style_profiles')
    op.drop_index('idx_writing_style_tenant', table_name='writing_style_profiles')
    op.drop_index('idx_writing_style_person', table_name='writing_style_profiles')
    op.drop_table('writing_style_profiles')
    op.drop_table('whatsapp_bots')
    op.drop_index(op.f('usage_analytics_tenant_id_idx'), table_name='usage_analytics')
    op.drop_index(op.f('usage_analytics_metric_type_idx'), table_name='usage_analytics')
    op.drop_index(op.f('usage_analytics_date_idx'), table_name='usage_analytics')
    op.drop_index(op.f('usage_analytics_agent_id_idx'), table_name='usage_analytics')
    op.drop_table('usage_analytics')
    op.drop_index(op.f('tenant_subscriptions_tenant_id_idx'), table_name='tenant_subscriptions')
    op.drop_index(op.f('tenant_subscriptions_stripe_subscription_id_idx'), table_name='tenant_subscriptions')
    op.drop_index(op.f('tenant_subscriptions_stripe_customer_id_idx'), table_name='tenant_subscriptions')
    op.drop_index(op.f('tenant_subscriptions_status_idx'), table_name='tenant_subscriptions')
    op.drop_index(op.f('tenant_subscriptions_plan_id_idx'), table_name='tenant_subscriptions')
    op.drop_table('tenant_subscriptions')
    op.drop_table('teams_bots')
    op.drop_index(op.f('social_auth_providers_tenant_id_idx'), table_name='social_auth_providers')
    op.drop_table('social_auth_providers')
    op.drop_table('slack_bots')
    op.drop_index(op.f('scheduled_tasks_tenant_id_idx'), table_name='scheduled_tasks')
    op.drop_index(op.f('scheduled_tasks_task_type_idx'), table_name='scheduled_tasks')
    op.drop_index(op.f('scheduled_tasks_next_run_at_idx'), table_name='scheduled_tasks')
    op.drop_index(op.f('scheduled_tasks_is_active_idx'), table_name='scheduled_tasks')
    op.drop_table('scheduled_tasks')
    op.drop_index(op.f('roles_tenant_id_idx'), table_name='roles')
    op.drop_index(op.f('roles_name_idx'), table_name='roles')
    op.drop_table('roles')
    op.drop_index(op.f('okta_tenants_tenant_id_idx'), table_name='okta_tenants')
    op.drop_table('okta_tenants')
    op.drop_index(op.f('oauth_apps_tenant_id_idx'), table_name='oauth_apps')
    op.drop_index(op.f('oauth_apps_provider_idx'), table_name='oauth_apps')
    op.drop_index(op.f('oauth_apps_id_idx'), table_name='oauth_apps')
    op.drop_table('oauth_apps')
    op.drop_index(op.f('knowledge_bases_tenant_id_idx'), table_name='knowledge_bases')
    op.drop_table('knowledge_bases')
    op.drop_index(op.f('database_connections_tenant_id_idx'), table_name='database_connections')
    op.drop_index(op.f('database_connections_status_idx'), table_name='database_connections')
    op.drop_index(op.f('database_connections_database_type_idx'), table_name='database_connections')
    op.drop_table('database_connections')
    op.drop_index(op.f('custom_tools_tenant_id_idx'), table_name='custom_tools')
    op.drop_index(op.f('custom_tools_name_idx'), table_name='custom_tools')
    op.drop_index(op.f('custom_tools_enabled_idx'), table_name='custom_tools')
    op.drop_table('custom_tools')
    op.drop_index(op.f('credit_topups_tenant_id_idx'), table_name='credit_topups')
    op.drop_index(op.f('credit_topups_stripe_payment_intent_id_idx'), table_name='credit_topups')
    op.drop_index(op.f('credit_topups_status_idx'), table_name='credit_topups')
    op.drop_table('credit_topups')
    op.drop_index(op.f('credit_balances_tenant_id_idx'), table_name='credit_balances')
    op.drop_table('credit_balances')
    op.drop_index(op.f('agent_widgets_tenant_id_idx'), table_name='agent_widgets')
    op.drop_index(op.f('agent_widgets_is_active_idx'), table_name='agent_widgets')
    op.drop_index(op.f('agent_widgets_api_key_idx'), table_name='agent_widgets')
    op.drop_index(op.f('agent_widgets_agent_id_idx'), table_name='agent_widgets')
    op.drop_table('agent_widgets')
    op.drop_index(op.f('agent_ratings_user_id_idx'), table_name='agent_ratings')
    op.drop_index(op.f('agent_ratings_agent_id_idx'), table_name='agent_ratings')
    op.drop_table('agent_ratings')
    op.drop_index(op.f('agent_pricing_tenant_id_idx'), table_name='agent_pricing')
    op.drop_index(op.f('agent_pricing_pricing_model_idx'), table_name='agent_pricing')
    op.drop_index(op.f('agent_pricing_is_active_idx'), table_name='agent_pricing')
    op.drop_index(op.f('agent_pricing_agent_id_idx'), table_name='agent_pricing')
    op.drop_table('agent_pricing')
    op.drop_index(op.f('agent_mcp_servers_mcp_server_id_idx'), table_name='agent_mcp_servers')
    op.drop_index(op.f('agent_mcp_servers_agent_id_idx'), table_name='agent_mcp_servers')
    op.drop_table('agent_mcp_servers')
    op.drop_index(op.f('agent_llm_configs_tenant_id_idx'), table_name='agent_llm_configs')
    op.drop_index(op.f('agent_llm_configs_is_default_idx'), table_name='agent_llm_configs')
    op.drop_index(op.f('agent_llm_configs_enabled_idx'), table_name='agent_llm_configs')
    op.drop_index(op.f('agent_llm_configs_agent_id_idx'), table_name='agent_llm_configs')
    op.drop_table('agent_llm_configs')
    op.drop_index(op.f('agent_domains_tenant_id_idx'), table_name='agent_domains')
    op.drop_index(op.f('agent_domains_subdomain_idx'), table_name='agent_domains')
    op.drop_index(op.f('agent_domains_domain_idx'), table_name='agent_domains')
    op.drop_index(op.f('agent_domains_agent_id_idx'), table_name='agent_domains')
    op.drop_table('agent_domains')
    op.drop_index(op.f('agent_context_files_tenant_id_idx'), table_name='agent_context_files')
    op.drop_index(op.f('agent_context_files_extraction_status_idx'), table_name='agent_context_files')
    op.drop_index(op.f('agent_context_files_agent_id_idx'), table_name='agent_context_files')
    op.drop_table('agent_context_files')
    op.drop_index(op.f('agent_api_keys_tenant_id_idx'), table_name='agent_api_keys')
    op.drop_index(op.f('agent_api_keys_key_prefix_idx'), table_name='agent_api_keys')
    op.drop_index(op.f('agent_api_keys_is_active_idx'), table_name='agent_api_keys')
    op.drop_index(op.f('agent_api_keys_expires_at_idx'), table_name='agent_api_keys')
    op.drop_index(op.f('agent_api_keys_api_key_idx'), table_name='agent_api_keys')
    op.drop_index(op.f('agent_api_keys_agent_id_idx'), table_name='agent_api_keys')
    op.drop_table('agent_api_keys')
    op.drop_index(op.f('voice_api_keys_tenant_id_idx'), table_name='voice_api_keys')
    op.drop_table('voice_api_keys')
    op.drop_index(op.f('tenants_status_idx'), table_name='tenants')
    op.drop_table('tenants')
    op.drop_index(op.f('subscription_plans_tier_idx'), table_name='subscription_plans')
    op.drop_index(op.f('subscription_plans_is_active_idx'), table_name='subscription_plans')
    op.drop_table('subscription_plans')
    op.drop_table('platform_settings')
    op.drop_index(op.f('permissions_resource_idx'), table_name='permissions')
    op.drop_index(op.f('permissions_name_idx'), table_name='permissions')
    op.drop_table('permissions')
    op.drop_index(op.f('mcp_servers_tenant_id_idx'), table_name='mcp_servers')
    op.drop_index(op.f('mcp_servers_status_idx'), table_name='mcp_servers')
    op.drop_index(op.f('mcp_servers_name_idx'), table_name='mcp_servers')
    op.drop_table('mcp_servers')
    op.drop_index(op.f('integration_configs_tenant_id_idx'), table_name='integration_configs')
    op.drop_index(op.f('integration_configs_provider_idx'), table_name='integration_configs')
    op.drop_index(op.f('integration_configs_integration_type_idx'), table_name='integration_configs')
    op.drop_table('integration_configs')
    op.drop_index(op.f('agents_tenant_id_idx'), table_name='agents')
    op.drop_index(op.f('agents_status_idx'), table_name='agents')
    op.drop_index(op.f('agents_parent_agent_id_idx'), table_name='agents')
    op.drop_index(op.f('agents_agent_name_idx'), table_name='agents')
    op.drop_table('agents')
    # ### end Alembic commands ###
