"""system governance settings and reusable tenant templates

Revision ID: 9d0e1f2a3b4c
Revises: 8c9d0e1f2a3b
Create Date: 2026-06-15 00:00:00.000000
"""
from __future__ import annotations

from datetime import datetime, timezone
import json

from alembic import op
import sqlalchemy as sa

revision = "9d0e1f2a3b4c"
down_revision = "8c9d0e1f2a3b"
branch_labels = None
depends_on = None
_RECONCILE_CREATE_ALL_TABLES = ("admin_governance_template_assignments", "admin_governance_templates", "core_system_settings")


def _now() -> datetime:
    return datetime.now(timezone.utc)


def upgrade() -> None:
    bind = op.get_bind()
    inspector = sa.inspect(bind)
    tables = set(inspector.get_table_names())

    # Reconcile only the empty create_all shape for the newly introduced tables.
    for table_name in _RECONCILE_CREATE_ALL_TABLES:
        if table_name in tables:
            quoted = bind.dialect.identifier_preparer.quote(table_name)
            count = bind.execute(sa.text(f"SELECT COUNT(*) FROM {quoted}")).scalar_one()  # nosemgrep: python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text
            if count:
                raise RuntimeError(f"Cannot reconcile non-empty create_all table {table_name}")
            op.drop_table(table_name)

    op.create_table(
        "core_system_settings",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("default_locale", sa.String(length=20), nullable=False, server_default="en"),
        sa.Column("allow_tenant_custom_groups", sa.Boolean(), nullable=False, server_default=sa.true()),
        sa.Column("allow_tenant_custom_roles", sa.Boolean(), nullable=False, server_default=sa.true()),
        sa.Column("allow_tenant_api_keys", sa.Boolean(), nullable=False, server_default=sa.true()),
        sa.Column("settings", sa.JSON(), nullable=False, server_default="{}"),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id"),
    )
    now = _now()
    bind.execute(sa.text(
        """
        INSERT INTO core_system_settings
            (id, default_locale, allow_tenant_custom_groups, allow_tenant_custom_roles,
             allow_tenant_api_keys, settings, created_at, updated_at)
        VALUES
            ('global', 'en', :allow_tenant_custom_groups, :allow_tenant_custom_roles,
             :allow_tenant_api_keys, '{}', :created_at, :updated_at)
        """
    ), {
        "allow_tenant_custom_groups": True,
        "allow_tenant_custom_roles": True,
        "allow_tenant_api_keys": True,
        "created_at": now,
        "updated_at": now,
    })

    op.create_table(
        "admin_governance_templates",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("kind", sa.String(length=20), nullable=False),
        sa.Column("slug", sa.String(length=100), nullable=False),
        sa.Column("name", sa.String(length=255), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("permissions", sa.JSON(), nullable=False, server_default="[]"),
        sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("kind", "slug", name="uq_governance_templates_kind_slug"),
    )
    op.create_index(op.f("ix_admin_governance_templates_kind"), "admin_governance_templates", ["kind"])

    op.create_table(
        "admin_governance_template_assignments",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("template_id", sa.String(length=36), nullable=False),
        sa.Column("tenant_id", sa.String(length=36), nullable=False),
        sa.Column("mode", sa.String(length=20), nullable=False, server_default="available"),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["template_id"], ["admin_governance_templates.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("template_id", "tenant_id", name="uq_governance_template_tenant"),
    )
    op.create_index(op.f("ix_admin_governance_template_assignments_template_id"), "admin_governance_template_assignments", ["template_id"])
    op.create_index(op.f("ix_admin_governance_template_assignments_tenant_id"), "admin_governance_template_assignments", ["tenant_id"])

    with op.batch_alter_table("tenancy_tenants") as batch_op:
        batch_op.add_column(sa.Column("allow_custom_groups", sa.Boolean(), nullable=True))
        batch_op.add_column(sa.Column("allow_custom_roles", sa.Boolean(), nullable=True))
        batch_op.add_column(sa.Column("allow_api_keys", sa.Boolean(), nullable=True))

    with op.batch_alter_table("access_groups") as batch_op:
        batch_op.add_column(sa.Column("system_template_id", sa.String(length=36), nullable=True))
        batch_op.add_column(sa.Column("system_required", sa.Boolean(), nullable=False, server_default=sa.false()))
        batch_op.create_foreign_key(
            "fk_groups_system_template_id_governance_templates",
            "admin_governance_templates", ["system_template_id"], ["id"], ondelete="SET NULL",
        )
    op.create_index(op.f("ix_access_groups_system_template_id"), "access_groups", ["system_template_id"])

    with op.batch_alter_table("access_roles") as batch_op:
        batch_op.add_column(sa.Column("system_template_id", sa.String(length=36), nullable=True))
        batch_op.add_column(sa.Column("system_required", sa.Boolean(), nullable=False, server_default=sa.false()))
        batch_op.create_foreign_key(
            "fk_roles_system_template_id_governance_templates",
            "admin_governance_templates", ["system_template_id"], ["id"], ondelete="SET NULL",
        )
    op.create_index(op.f("ix_access_roles_system_template_id"), "access_roles", ["system_template_id"])

    # Existing system owners use system:* and need no data change. Extend the
    # read-only built-in auditor role to the newly introduced read scopes.
    auditor = bind.execute(sa.text(
        "SELECT id, permissions FROM access_roles WHERE tenant_id IS NULL AND slug = 'system_auditor'"
    )).mappings().first()
    if auditor:
        raw_permissions = auditor["permissions"] or []
        permissions = json.loads(raw_permissions) if isinstance(raw_permissions, str) else list(raw_permissions)
        for scope in ("system:settings:read", "system:governance:read"):
            if scope not in permissions:
                permissions.append(scope)
        roles_table = sa.table(
            "access_roles",
            sa.column("id", sa.String),
            sa.column("permissions", sa.JSON),
            sa.column("updated_at", sa.DateTime(timezone=True)),
        )
        bind.execute(
            roles_table.update().where(roles_table.c.id == auditor["id"]).values(
                permissions=permissions, updated_at=now
            )
        )


def downgrade() -> None:
    op.drop_index(op.f("ix_access_roles_system_template_id"), table_name="access_roles")
    with op.batch_alter_table("access_roles") as batch_op:
        batch_op.drop_constraint("fk_roles_system_template_id_governance_templates", type_="foreignkey")
        batch_op.drop_column("system_required")
        batch_op.drop_column("system_template_id")

    op.drop_index(op.f("ix_access_groups_system_template_id"), table_name="access_groups")
    with op.batch_alter_table("access_groups") as batch_op:
        batch_op.drop_constraint("fk_groups_system_template_id_governance_templates", type_="foreignkey")
        batch_op.drop_column("system_required")
        batch_op.drop_column("system_template_id")

    with op.batch_alter_table("tenancy_tenants") as batch_op:
        batch_op.drop_column("allow_api_keys")
        batch_op.drop_column("allow_custom_roles")
        batch_op.drop_column("allow_custom_groups")

    op.drop_index(op.f("ix_admin_governance_template_assignments_tenant_id"), table_name="admin_governance_template_assignments")
    op.drop_index(op.f("ix_admin_governance_template_assignments_template_id"), table_name="admin_governance_template_assignments")
    op.drop_table("admin_governance_template_assignments")
    op.drop_index(op.f("ix_admin_governance_templates_kind"), table_name="admin_governance_templates")
    op.drop_table("admin_governance_templates")
    op.drop_table("core_system_settings")
