"""Web chat: web_conversations and web_conversation_messages tables with RLS

Revision ID: 008
Revises: 007
Create Date: 2026-03-25

This migration:
1. Creates the web_conversations table (tenant-scoped, RLS-enabled)
2. Creates the web_conversation_messages table (CASCADE delete, RLS-enabled)
3. Enables FORCE ROW LEVEL SECURITY on both tables
4. Creates tenant_isolation RLS policies matching existing pattern
5. Adds index on web_conversation_messages(conversation_id, created_at) for pagination
6. Replaces the channel_type CHECK constraint on channel_connections to include 'web'

NOTE on CHECK constraint replacement (Pitfall 5):
  The existing constraint chk_channel_type only covers the original 7 channels.
  ALTER TABLE DROP CONSTRAINT + ADD CONSTRAINT is used instead of just adding a
  new constraint — the old constraint remains active otherwise and would reject 'web'.
"""

from __future__ import annotations

from typing import Sequence, Union

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

# Alembic migration metadata
revision: str = "008"
down_revision: Union[str, None] = "007"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

# All valid channel types including new 'web' — must match ChannelType StrEnum in message.py
_CHANNEL_TYPES = (
    "slack", "whatsapp", "mattermost", "rocketchat", "teams", "telegram", "signal", "web"
)


def upgrade() -> None:
    # -------------------------------------------------------------------------
    # 1. Create web_conversations table
    # -------------------------------------------------------------------------
    op.create_table(
        "web_conversations",
        sa.Column("id", UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
        sa.Column(
            "tenant_id",
            UUID(as_uuid=True),
            sa.ForeignKey("tenants.id", ondelete="CASCADE"),
            nullable=False,
        ),
        sa.Column(
            "agent_id",
            UUID(as_uuid=True),
            sa.ForeignKey("agents.id", ondelete="CASCADE"),
            nullable=False,
        ),
        sa.Column("user_id", UUID(as_uuid=True), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("NOW()"),
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("NOW()"),
        ),
        sa.UniqueConstraint(
            "tenant_id",
            "agent_id",
            "user_id",
            name="uq_web_conversations_tenant_agent_user",
        ),
    )
    op.create_index("ix_web_conversations_tenant_id", "web_conversations", ["tenant_id"])

    # Enable RLS on web_conversations
    op.execute("ALTER TABLE web_conversations ENABLE ROW LEVEL SECURITY")
    op.execute("ALTER TABLE web_conversations FORCE ROW LEVEL SECURITY")
    op.execute("""
        CREATE POLICY tenant_isolation ON web_conversations
        USING (tenant_id = current_setting('app.current_tenant', TRUE)::uuid)
    """)

    # -------------------------------------------------------------------------
    # 2. Create web_conversation_messages table
    # -------------------------------------------------------------------------
    op.create_table(
        "web_conversation_messages",
        sa.Column("id", UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
        sa.Column(
            "conversation_id",
            UUID(as_uuid=True),
            sa.ForeignKey("web_conversations.id", ondelete="CASCADE"),
            nullable=False,
        ),
        sa.Column("tenant_id", UUID(as_uuid=True), nullable=False),
        sa.Column("role", sa.Text, nullable=False),
        sa.Column("content", sa.Text, nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("NOW()"),
        ),
    )

    # CHECK constraint on role — TEXT+CHECK per Phase 1 convention (not sa.Enum)
    op.execute(
        "ALTER TABLE web_conversation_messages ADD CONSTRAINT chk_message_role "
        "CHECK (role IN ('user', 'assistant'))"
    )

    # Index for paginated message history queries: ORDER BY created_at with conversation filter
    op.create_index(
        "ix_web_conversation_messages_conv_created",
        "web_conversation_messages",
        ["conversation_id", "created_at"],
    )

    # Enable RLS on web_conversation_messages
    op.execute("ALTER TABLE web_conversation_messages ENABLE ROW LEVEL SECURITY")
    op.execute("ALTER TABLE web_conversation_messages FORCE ROW LEVEL SECURITY")
    op.execute("""
        CREATE POLICY tenant_isolation ON web_conversation_messages
        USING (tenant_id = current_setting('app.current_tenant', TRUE)::uuid)
    """)

    # -------------------------------------------------------------------------
    # 3. Grant permissions to konstruct_app
    # -------------------------------------------------------------------------
    op.execute("GRANT SELECT, INSERT, UPDATE, DELETE ON web_conversations TO konstruct_app")
    op.execute("GRANT SELECT, INSERT, UPDATE, DELETE ON web_conversation_messages TO konstruct_app")

    # -------------------------------------------------------------------------
    # 4. Update channel_connections CHECK constraint to include 'web'
    #
    # DROP + re-ADD because an existing CHECK constraint still enforces the old
    # set of values — simply adding a second constraint would AND them together.
    # -------------------------------------------------------------------------
    op.execute("ALTER TABLE channel_connections DROP CONSTRAINT IF EXISTS chk_channel_type")
    op.execute(
        "ALTER TABLE channel_connections ADD CONSTRAINT chk_channel_type "
        f"CHECK (channel_type IN {tuple(_CHANNEL_TYPES)})"
    )


def downgrade() -> None:
    # Restore original channel_type CHECK constraint (without 'web')
    _ORIGINAL_CHANNEL_TYPES = (
        "slack", "whatsapp", "mattermost", "rocketchat", "teams", "telegram", "signal"
    )
    op.execute("ALTER TABLE channel_connections DROP CONSTRAINT IF EXISTS chk_channel_type")
    op.execute(
        "ALTER TABLE channel_connections ADD CONSTRAINT chk_channel_type "
        f"CHECK (channel_type IN {tuple(_ORIGINAL_CHANNEL_TYPES)})"
    )

    # Drop web_conversation_messages first (FK dependency)
    op.execute("REVOKE ALL ON web_conversation_messages FROM konstruct_app")
    op.drop_index("ix_web_conversation_messages_conv_created")
    op.drop_table("web_conversation_messages")

    # Drop web_conversations
    op.execute("REVOKE ALL ON web_conversations FROM konstruct_app")
    op.drop_index("ix_web_conversations_tenant_id")
    op.drop_table("web_conversations")
