<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
                      http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">

  <!--
    Webhook subscriptions (v2 feature) — HTTP callback delivery параллельно
    с Kafka publishing. Для consumer'ов которые не могут Kafka subscribe
    (геопортал PHP, legacy admin-ui'и заказчиков, BI-системы).

    Архитектура:
    - WebhookDispatcher (@Scheduled @1s) читает outbox_events × subscriptions
      → создаёт webhook_deliveries → POST URL → mark delivered.
    - Параллельно с OutboxPoller (Kafka), не вместо.
    - HMAC-SHA256 signing: header `X-Ordinis-Signature: sha256=<hex>`,
      key = subscriptions.hmac_secret (random 32 bytes generated на create).
    - SSRF guard: deny private CIDR (app-level, не БД).
  -->

  <changeSet id="0013-1-webhook-subscriptions" author="ordinis">
    <createTable tableName="webhook_subscriptions">
      <column name="id" type="UUID" defaultValueComputed="gen_random_uuid()">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="name" type="VARCHAR(128)">
        <constraints nullable="false"/>
      </column>
      <column name="url" type="VARCHAR(2048)">
        <constraints nullable="false"/>
      </column>
      <!-- Filter arrays: NULL/empty = match all. PG TEXT[] для удобства
           index-aware queries (ANY/= operators). -->
      <column name="scope_filter" type="TEXT[]"/>
      <column name="dictionary_filter" type="TEXT[]"/>
      <column name="event_type_filter" type="TEXT[]"/>
      <!-- HMAC secret hex-encoded 64 chars (32 bytes random). Generated
           server-side, returned ONCE на create response, потом hidden. -->
      <column name="hmac_secret" type="VARCHAR(128)">
        <constraints nullable="false"/>
      </column>
      <column name="active" type="BOOLEAN" defaultValueBoolean="true">
        <constraints nullable="false"/>
      </column>
      <column name="created_at" type="TIMESTAMPTZ" defaultValueComputed="now()">
        <constraints nullable="false"/>
      </column>
      <column name="created_by" type="VARCHAR(128)">
        <constraints nullable="false"/>
      </column>
      <column name="updated_at" type="TIMESTAMPTZ" defaultValueComputed="now()">
        <constraints nullable="false"/>
      </column>
      <column name="description" type="TEXT"/>
    </createTable>
  </changeSet>

  <changeSet id="0013-2-webhook-subscriptions-checks" author="ordinis">
    <comment>
      URL должен начинаться с https:// (production) или http:// (dev).
      Scope values constrained тот же enum что DataScope в Java.
    </comment>
    <sql>
      ALTER TABLE webhook_subscriptions
      ADD CONSTRAINT chk_webhook_url_scheme
      CHECK (url ~ '^https?://[^\s]+$');

      ALTER TABLE webhook_subscriptions
      ADD CONSTRAINT chk_webhook_scope_filter
      CHECK (scope_filter IS NULL OR scope_filter &lt;@ ARRAY['PUBLIC','INTERNAL','RESTRICTED']::TEXT[]);
    </sql>
    <rollback>
      <sql>
        ALTER TABLE webhook_subscriptions DROP CONSTRAINT IF EXISTS chk_webhook_url_scheme;
        ALTER TABLE webhook_subscriptions DROP CONSTRAINT IF EXISTS chk_webhook_scope_filter;
      </sql>
    </rollback>
  </changeSet>

  <changeSet id="0013-3-webhook-subscriptions-active-index" author="ordinis">
    <comment>
      Partial index — dispatcher poller сканит только активные subscriptions
      (горячий path). Inactive sit без overhead.
    </comment>
    <sql>
      CREATE INDEX idx_webhook_subscriptions_active
      ON webhook_subscriptions (created_at)
      WHERE active = true;
    </sql>
    <rollback>
      <sql>DROP INDEX IF EXISTS idx_webhook_subscriptions_active;</sql>
    </rollback>
  </changeSet>

</databaseChangeLog>
