"""initial schema part 1: foundation tables

Revision ID: 202602260001
Revises:
Create Date: 2026-02-26 20:10:00
"""

from typing import Sequence, Union

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

revision: str = "202602260001"
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    op.create_table(
        "llm_factory",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("name", sa.String(length=50), nullable=False),
        sa.Column("request_url", sa.String(length=255), nullable=False),
        sa.Column("avatar", 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.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("name"),
    )
    _enable_rls("llm_factory")

    op.create_table(
        "llms",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("factory_id", sa.UUID(), nullable=False),
        sa.Column("model_code", sa.String(length=50), nullable=False),
        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.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("model_code"),
    )
    op.create_index("ix_llms_factory_id", "llms", ["factory_id"], unique=False)
    op.create_foreign_key(
        "fk_llms_factory_id",
        "llms",
        "llm_factory",
        ["factory_id"],
        ["id"],
        ondelete="RESTRICT",
    )
    _enable_rls("llms")

    op.create_table(
        "profiles",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("username", sa.String(length=30), nullable=False),
        sa.Column("avatar_url", sa.Text(), nullable=True),
        sa.Column("bio", sa.String(length=200), nullable=True),
        sa.Column(
            "settings",
            postgresql.JSONB(astext_type=sa.Text()),
            server_default="{}",
            nullable=False,
        ),
        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.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index("ix_profiles_username", "profiles", ["username"], unique=False)
    op.create_index(
        "ix_profiles_settings_gin",
        "profiles",
        ["settings"],
        unique=False,
        postgresql_using="gin",
    )
    op.create_foreign_key(
        "fk_profiles_id",
        "profiles",
        "users",
        ["id"],
        ["id"],
        referent_schema="auth",
        ondelete="CASCADE",
    )
    _enable_rls("profiles")
    op.execute(
        """
        CREATE OR REPLACE FUNCTION public.create_profile_for_new_user()
        RETURNS trigger
        LANGUAGE plpgsql
        SECURITY DEFINER
        SET search_path = public
        AS $$
        BEGIN
            INSERT INTO public.profiles (id, username, avatar_url, bio, settings, created_at, updated_at)
            VALUES (
                NEW.id,
                COALESCE(
                    NEW.raw_user_meta_data ->> 'username',
                    split_part(NEW.email, '@', 1),
                    'user_' || substring(NEW.id::text, 1, 8)
                ),
                NULL,
                NULL,
                '{}'::jsonb,
                now(),
                now()
            )
            ON CONFLICT (id) DO NOTHING;

            RETURN NEW;
        END;
        $$;
        """
    )
    op.execute("DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users")
    op.execute(
        """
        CREATE TRIGGER on_auth_user_created
        AFTER INSERT ON auth.users
        FOR EACH ROW EXECUTE FUNCTION public.create_profile_for_new_user();
        """
    )

    op.create_table(
        "automation_jobs",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("owner_id", sa.UUID(), nullable=False),
        sa.Column("title", sa.String(length=255), nullable=False),
        sa.Column("prompt", sa.Text(), nullable=False),
        sa.Column("schedule_type", sa.String(length=20), nullable=False),
        sa.Column("run_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("next_run_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("timezone", sa.String(length=50), nullable=False),
        sa.Column("last_run_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("status", sa.String(length=20), nullable=False),
        sa.Column("created_by", sa.UUID(), 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.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("id", "owner_id", name="uq_automation_jobs_id_owner"),
    )
    op.create_index(
        "ix_automation_jobs_owner_status",
        "automation_jobs",
        ["owner_id", "status"],
        unique=False,
    )
    op.create_index(
        "ix_automation_jobs_status_next_run",
        "automation_jobs",
        ["status", "next_run_at"],
        unique=False,
    )
    op.execute(
        "ALTER TABLE automation_jobs ADD CONSTRAINT chk_automation_job_schedule_type CHECK (schedule_type IN ('daily', 'weekly'))"
    )
    op.execute(
        "ALTER TABLE automation_jobs ADD CONSTRAINT chk_automation_job_status CHECK (status IN ('active', 'disabled'))"
    )
    op.create_foreign_key(
        "fk_automation_jobs_owner_id",
        "automation_jobs",
        "users",
        ["owner_id"],
        ["id"],
        referent_schema="auth",
        ondelete="CASCADE",
    )
    op.create_foreign_key(
        "fk_automation_jobs_created_by",
        "automation_jobs",
        "users",
        ["created_by"],
        ["id"],
        referent_schema="auth",
        ondelete="SET NULL",
    )
    _enable_rls("automation_jobs")

    op.execute("REVOKE ALL ON TABLE public.alembic_version FROM anon")
    op.execute("REVOKE ALL ON TABLE public.alembic_version FROM authenticated")


def downgrade() -> None:
    _drop_rls("automation_jobs")
    op.drop_constraint(
        "fk_automation_jobs_created_by", "automation_jobs", type_="foreignkey"
    )
    op.drop_constraint(
        "fk_automation_jobs_owner_id", "automation_jobs", type_="foreignkey"
    )
    op.drop_table("automation_jobs")

    _drop_rls("profiles")
    op.execute("DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users")
    op.execute("DROP FUNCTION IF EXISTS public.create_profile_for_new_user()")
    op.drop_constraint("fk_profiles_id", "profiles", type_="foreignkey")
    op.drop_table("profiles")

    _drop_rls("llms")
    op.drop_constraint("fk_llms_factory_id", "llms", type_="foreignkey")
    op.drop_table("llms")
    _drop_rls("llm_factory")
    op.drop_table("llm_factory")


def _enable_rls(table_name: str) -> None:
    for role in ["anon", "authenticated"]:
        for action in ["select", "insert", "update", "delete"]:
            op.execute(
                f"DROP POLICY IF EXISTS {role}_{action}_{table_name} ON {table_name}"
            )
    op.execute(f"ALTER TABLE {table_name} ENABLE ROW LEVEL SECURITY")
    for role in ["anon", "authenticated"]:
        op.execute(
            f"CREATE POLICY {role}_select_{table_name} ON {table_name} FOR SELECT TO {role} USING (false)"
        )
        op.execute(
            f"CREATE POLICY {role}_insert_{table_name} ON {table_name} FOR INSERT TO {role} WITH CHECK (false)"
        )
        op.execute(
            f"CREATE POLICY {role}_update_{table_name} ON {table_name} FOR UPDATE TO {role} USING (false) WITH CHECK (false)"
        )
        op.execute(
            f"CREATE POLICY {role}_delete_{table_name} ON {table_name} FOR DELETE TO {role} USING (false)"
        )


def _drop_rls(table_name: str) -> None:
    for role in ["anon", "authenticated"]:
        op.execute(f"DROP POLICY IF EXISTS {role}_delete_{table_name} ON {table_name}")
        op.execute(f"DROP POLICY IF EXISTS {role}_update_{table_name} ON {table_name}")
        op.execute(f"DROP POLICY IF EXISTS {role}_insert_{table_name} ON {table_name}")
        op.execute(f"DROP POLICY IF EXISTS {role}_select_{table_name} ON {table_name}")
    op.execute(f"ALTER TABLE {table_name} DISABLE ROW LEVEL SECURITY")
