<?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">

  <!--
    User display cache (sub → username/email/name) — persistent backing store
    for UserDisplayService.

    Цель: остановить bleeding "ток мой UUID отображается" в audit/reviews/
    history/changelog. Раньше UserDisplayService держал данные in-memory →
    pod restart терял всё → users которые редко логинились показывались
    как UUID.

    Three-tier resolve chain (после wire-up в UserDisplayService):
      1. JVM ConcurrentHashMap (hot cache, ~µs)
      2. user_display_cache table (этот migration, ~ms)
      3. Keycloak Admin API on-demand (~10-50ms, only if not in DB)
         + nightly scheduled bulk sync to refill (Phase 3).

    Decisions:
    - PK = sub (UUID-like Keycloak user id). Match'ит JWT sub claim
      directly — нет surrogate id.
    - source ENUM текстом ('JWT_CAPTURE' | 'KEYCLOAK_SYNC' |
      'KEYCLOAK_ON_DEMAND') — Hibernate @Enumerated(STRING) уrok из 0020.
    - synced_at = когда последний раз обновили из источника. updated_at =
      row mtime (Hibernate @UpdateTimestamp). Разные потому что
      KEYCLOAK_SYNC bulk job обновляет каждые 6h даже если данные не
      изменились.
    - Soft cap on rows не нужен — Keycloak realm пока ~50 users, table
      будет ≤ 1k entries обозримо.
  -->

  <changeSet id="0023-1-user-display-cache" author="ordinis">
    <comment>user_display_cache — persistent sub → display info, three-tier resolve</comment>
    <createTable tableName="user_display_cache">
      <column name="sub" type="VARCHAR(64)">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="preferred_username" type="VARCHAR(255)">
        <constraints nullable="true"/>
      </column>
      <column name="name" type="VARCHAR(255)">
        <constraints nullable="true"/>
      </column>
      <column name="email" type="VARCHAR(320)"><!-- RFC 5321 max -->
        <constraints nullable="true"/>
      </column>
      <column name="source" type="VARCHAR(32)" defaultValue="JWT_CAPTURE">
        <constraints nullable="false"/>
      </column>
      <column name="synced_at" type="TIMESTAMP WITH TIME ZONE" defaultValueComputed="NOW()">
        <constraints nullable="false"/>
      </column>
      <column name="updated_at" type="TIMESTAMP WITH TIME ZONE" defaultValueComputed="NOW()">
        <constraints nullable="false"/>
      </column>
    </createTable>
  </changeSet>

  <changeSet id="0023-2-user-display-cache-source-check" author="ordinis">
    <comment>Enforce source enum values inline в DB чтобы insert с typo шумел</comment>
    <sql>
      ALTER TABLE user_display_cache
        ADD CONSTRAINT user_display_cache_source_check
        CHECK (source IN ('JWT_CAPTURE', 'KEYCLOAK_SYNC', 'KEYCLOAK_ON_DEMAND'));
    </sql>
    <rollback>
      ALTER TABLE user_display_cache DROP CONSTRAINT user_display_cache_source_check;
    </rollback>
  </changeSet>

  <changeSet id="0023-3-user-display-cache-synced-index" author="ordinis">
    <comment>Index for Phase 3 nightly sync — pull stale entries by synced_at</comment>
    <createIndex tableName="user_display_cache" indexName="idx_user_display_synced_at">
      <column name="synced_at"/>
    </createIndex>
  </changeSet>
</databaseChangeLog>
