"""Initial schema with RLS policies for multi-tenant isolation.

Revision ID: 001
Revises: None
Create Date: 2026-02-24

This migration creates:
1. All database tables (tenants, users, devices, device_groups, device_tags,
   device_group_memberships, device_tag_assignments)
2. Composite unique indexes for tenant-scoped uniqueness
3. Row Level Security (RLS) on all tenant-scoped tables
4. RLS policies using app.current_tenant PostgreSQL setting
5. The app_user role with appropriate grants (cannot bypass RLS)
"""

from typing import Sequence, Union

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

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


def upgrade() -> None:
    # =========================================================================
    # TENANTS TABLE
    # =========================================================================
    op.create_table(
        "tenants",
        sa.Column(
            "id",
            postgresql.UUID(as_uuid=True),
            server_default=sa.text("gen_random_uuid()"),
            nullable=False,
        ),
        sa.Column("name", sa.String(255), nullable=False),
        sa.Column("description", sa.Text, nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("name"),
    )
    op.create_index("ix_tenants_name", "tenants", ["name"], unique=True)

    # =========================================================================
    # USERS TABLE
    # =========================================================================
    op.create_table(
        "users",
        sa.Column(
            "id",
            postgresql.UUID(as_uuid=True),
            server_default=sa.text("gen_random_uuid()"),
            nullable=False,
        ),
        sa.Column("email", sa.String(255), nullable=False),
        sa.Column("hashed_password", sa.String(255), nullable=False),
        sa.Column("name", sa.String(255), nullable=False),
        sa.Column("role", sa.String(50), nullable=False, server_default="viewer"),
        sa.Column("tenant_id", postgresql.UUID(as_uuid=True), nullable=True),
        sa.Column("is_active", sa.Boolean, nullable=False, server_default="true"),
        sa.Column("last_login", sa.DateTime(timezone=True), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("email"),
        sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
    )
    op.create_index("ix_users_email", "users", ["email"], unique=True)
    op.create_index("ix_users_tenant_id", "users", ["tenant_id"])

    # =========================================================================
    # DEVICES TABLE
    # =========================================================================
    op.create_table(
        "devices",
        sa.Column(
            "id",
            postgresql.UUID(as_uuid=True),
            server_default=sa.text("gen_random_uuid()"),
            nullable=False,
        ),
        sa.Column("tenant_id", postgresql.UUID(as_uuid=True), nullable=False),
        sa.Column("hostname", sa.String(255), nullable=False),
        sa.Column("ip_address", sa.String(45), nullable=False),
        sa.Column("api_port", sa.Integer, nullable=False, server_default="8728"),
        sa.Column("api_ssl_port", sa.Integer, nullable=False, server_default="8729"),
        sa.Column("model", sa.String(255), nullable=True),
        sa.Column("serial_number", sa.String(255), nullable=True),
        sa.Column("firmware_version", sa.String(100), nullable=True),
        sa.Column("routeros_version", sa.String(100), nullable=True),
        sa.Column("uptime_seconds", sa.Integer, nullable=True),
        sa.Column("last_seen", sa.DateTime(timezone=True), nullable=True),
        sa.Column("encrypted_credentials", sa.LargeBinary, nullable=True),
        sa.Column("status", sa.String(20), nullable=False, server_default="unknown"),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
        sa.UniqueConstraint("tenant_id", "hostname", name="uq_devices_tenant_hostname"),
    )
    op.create_index("ix_devices_tenant_id", "devices", ["tenant_id"])

    # =========================================================================
    # DEVICE GROUPS TABLE
    # =========================================================================
    op.create_table(
        "device_groups",
        sa.Column(
            "id",
            postgresql.UUID(as_uuid=True),
            server_default=sa.text("gen_random_uuid()"),
            nullable=False,
        ),
        sa.Column("tenant_id", postgresql.UUID(as_uuid=True), nullable=False),
        sa.Column("name", sa.String(255), nullable=False),
        sa.Column("description", sa.Text, nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=False,
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
        sa.UniqueConstraint("tenant_id", "name", name="uq_device_groups_tenant_name"),
    )
    op.create_index("ix_device_groups_tenant_id", "device_groups", ["tenant_id"])

    # =========================================================================
    # DEVICE TAGS TABLE
    # =========================================================================
    op.create_table(
        "device_tags",
        sa.Column(
            "id",
            postgresql.UUID(as_uuid=True),
            server_default=sa.text("gen_random_uuid()"),
            nullable=False,
        ),
        sa.Column("tenant_id", postgresql.UUID(as_uuid=True), nullable=False),
        sa.Column("name", sa.String(100), nullable=False),
        sa.Column("color", sa.String(7), nullable=True),
        sa.PrimaryKeyConstraint("id"),
        sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
        sa.UniqueConstraint("tenant_id", "name", name="uq_device_tags_tenant_name"),
    )
    op.create_index("ix_device_tags_tenant_id", "device_tags", ["tenant_id"])

    # =========================================================================
    # DEVICE GROUP MEMBERSHIPS TABLE
    # =========================================================================
    op.create_table(
        "device_group_memberships",
        sa.Column("device_id", postgresql.UUID(as_uuid=True), nullable=False),
        sa.Column("group_id", postgresql.UUID(as_uuid=True), nullable=False),
        sa.PrimaryKeyConstraint("device_id", "group_id"),
        sa.ForeignKeyConstraint(["device_id"], ["devices.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["group_id"], ["device_groups.id"], ondelete="CASCADE"),
    )

    # =========================================================================
    # DEVICE TAG ASSIGNMENTS TABLE
    # =========================================================================
    op.create_table(
        "device_tag_assignments",
        sa.Column("device_id", postgresql.UUID(as_uuid=True), nullable=False),
        sa.Column("tag_id", postgresql.UUID(as_uuid=True), nullable=False),
        sa.PrimaryKeyConstraint("device_id", "tag_id"),
        sa.ForeignKeyConstraint(["device_id"], ["devices.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["tag_id"], ["device_tags.id"], ondelete="CASCADE"),
    )

    # =========================================================================
    # ROW LEVEL SECURITY (RLS)
    # =========================================================================
    # RLS is the core tenant isolation mechanism. The app_user role CANNOT
    # bypass RLS (only superusers can). All queries through app_user will
    # be filtered by the current_setting('app.current_tenant') value which
    # is set per-request by the tenant_context middleware.

    conn = op.get_bind()

    # --- TENANTS RLS ---
    # Super admin sees all; tenant users see only their tenant
    conn.execute(sa.text("ALTER TABLE tenants ENABLE ROW LEVEL SECURITY"))
    conn.execute(sa.text("ALTER TABLE tenants FORCE ROW LEVEL SECURITY"))
    conn.execute(sa.text("""
        CREATE POLICY tenant_isolation ON tenants
        USING (
            id::text = current_setting('app.current_tenant', true)
            OR current_setting('app.current_tenant', true) = 'super_admin'
        )
        WITH CHECK (
            id::text = current_setting('app.current_tenant', true)
            OR current_setting('app.current_tenant', true) = 'super_admin'
        )
    """))

    # --- USERS RLS ---
    # Users see only other users in their tenant; super_admin sees all
    conn.execute(sa.text("ALTER TABLE users ENABLE ROW LEVEL SECURITY"))
    conn.execute(sa.text("ALTER TABLE users FORCE ROW LEVEL SECURITY"))
    conn.execute(sa.text("""
        CREATE POLICY tenant_isolation ON users
        USING (
            tenant_id::text = current_setting('app.current_tenant', true)
            OR current_setting('app.current_tenant', true) = 'super_admin'
        )
        WITH CHECK (
            tenant_id::text = current_setting('app.current_tenant', true)
            OR current_setting('app.current_tenant', true) = 'super_admin'
        )
    """))

    # --- DEVICES RLS ---
    conn.execute(sa.text("ALTER TABLE devices ENABLE ROW LEVEL SECURITY"))
    conn.execute(sa.text("ALTER TABLE devices FORCE ROW LEVEL SECURITY"))
    conn.execute(sa.text("""
        CREATE POLICY tenant_isolation ON devices
        USING (tenant_id::text = current_setting('app.current_tenant', true))
        WITH CHECK (tenant_id::text = current_setting('app.current_tenant', true))
    """))

    # --- DEVICE GROUPS RLS ---
    conn.execute(sa.text("ALTER TABLE device_groups ENABLE ROW LEVEL SECURITY"))
    conn.execute(sa.text("ALTER TABLE device_groups FORCE ROW LEVEL SECURITY"))
    conn.execute(sa.text("""
        CREATE POLICY tenant_isolation ON device_groups
        USING (tenant_id::text = current_setting('app.current_tenant', true))
        WITH CHECK (tenant_id::text = current_setting('app.current_tenant', true))
    """))

    # --- DEVICE TAGS RLS ---
    conn.execute(sa.text("ALTER TABLE device_tags ENABLE ROW LEVEL SECURITY"))
    conn.execute(sa.text("ALTER TABLE device_tags FORCE ROW LEVEL SECURITY"))
    conn.execute(sa.text("""
        CREATE POLICY tenant_isolation ON device_tags
        USING (tenant_id::text = current_setting('app.current_tenant', true))
        WITH CHECK (tenant_id::text = current_setting('app.current_tenant', true))
    """))

    # --- DEVICE GROUP MEMBERSHIPS RLS ---
    # These are filtered by joining through devices/groups (which already have RLS)
    # But we also add direct RLS via a join to the devices table
    conn.execute(sa.text("ALTER TABLE device_group_memberships ENABLE ROW LEVEL SECURITY"))
    conn.execute(sa.text("ALTER TABLE device_group_memberships FORCE ROW LEVEL SECURITY"))
    conn.execute(sa.text("""
        CREATE POLICY tenant_isolation ON device_group_memberships
        USING (
            EXISTS (
                SELECT 1 FROM devices d
                WHERE d.id = device_id
                AND d.tenant_id::text = current_setting('app.current_tenant', true)
            )
        )
        WITH CHECK (
            EXISTS (
                SELECT 1 FROM devices d
                WHERE d.id = device_id
                AND d.tenant_id::text = current_setting('app.current_tenant', true)
            )
        )
    """))

    # --- DEVICE TAG ASSIGNMENTS RLS ---
    conn.execute(sa.text("ALTER TABLE device_tag_assignments ENABLE ROW LEVEL SECURITY"))
    conn.execute(sa.text("ALTER TABLE device_tag_assignments FORCE ROW LEVEL SECURITY"))
    conn.execute(sa.text("""
        CREATE POLICY tenant_isolation ON device_tag_assignments
        USING (
            EXISTS (
                SELECT 1 FROM devices d
                WHERE d.id = device_id
                AND d.tenant_id::text = current_setting('app.current_tenant', true)
            )
        )
        WITH CHECK (
            EXISTS (
                SELECT 1 FROM devices d
                WHERE d.id = device_id
                AND d.tenant_id::text = current_setting('app.current_tenant', true)
            )
        )
    """))

    # =========================================================================
    # GRANT PERMISSIONS TO app_user (RLS-enforcing application role)
    # =========================================================================
    # app_user is a non-superuser role — it CANNOT bypass RLS policies.
    # All API queries use this role to ensure tenant isolation.

    tables = [
        "tenants",
        "users",
        "devices",
        "device_groups",
        "device_tags",
        "device_group_memberships",
        "device_tag_assignments",
    ]

    for table in tables:
        conn.execute(sa.text(
            f"GRANT SELECT, INSERT, UPDATE, DELETE ON {table} TO app_user"
        ))

    # Grant sequence usage for UUID generation (gen_random_uuid is built-in, but just in case)
    conn.execute(sa.text("GRANT USAGE ON SCHEMA public TO app_user"))

    # Allow app_user to set the tenant context variable
    conn.execute(sa.text("GRANT SET ON PARAMETER app.current_tenant TO app_user"))


def downgrade() -> None:
    conn = op.get_bind()

    # Revoke grants
    tables = [
        "tenants",
        "users",
        "devices",
        "device_groups",
        "device_tags",
        "device_group_memberships",
        "device_tag_assignments",
    ]
    for table in tables:
        try:
            conn.execute(sa.text(f"REVOKE ALL ON {table} FROM app_user"))
        except Exception:
            pass

    # Drop tables (in reverse dependency order)
    op.drop_table("device_tag_assignments")
    op.drop_table("device_group_memberships")
    op.drop_table("device_tags")
    op.drop_table("device_groups")
    op.drop_table("devices")
    op.drop_table("users")
    op.drop_table("tenants")
