-- 187_web_support_chat.sql
-- Web-чат поддержки (сайт МЕРА) поверх УЖЕ существующего Telegram support-моста
-- (data/sql/186_tg_support.sql, app/services/tgbot/bridge.py). Источник обращения
-- меняется (сайт вместо Telegram-лички клиента), маршрутизация ответа оператора
-- (реплай на зеркало в топике супергруппы) остаётся ТОЙ ЖЕ — оператор ничего
-- нового не учит.
--
-- ПОЧЕМУ ОТДЕЛЬНЫЕ ТАБЛИЦЫ, А НЕ "tg_support_* + channel"
-- (взвешено явно, per code review requirement):
--
--   Вариант А (отклонён) — добавить channel text ('telegram'|'web') в
--   tg_support_users/tg_support_messages:
--     - tg_support_users.chat_id bigint PRIMARY KEY — это Telegram private
--       chat id клиента. У веб-пользователя сайта ЕГО НЕТ (клиент никогда не
--       писал боту в личку) — пришлось бы либо (а) городить синтетический
--       chat_id для веб-юзера (напр. отрицательный hash от username) — это
--       вводит ВТОРУЮ систему идентификации внутри одной PK-колонки,
--       семантика которой документирована как "Telegram chat id" (186:54),
--       либо (б) делать chat_id NULLABLE и городить ещё одну колонку
--       username NULLABLE рядом — таблица с двумя взаимоисключающими
--       identity-схемами и кучей CHECK-ограничений вида
--       "chat_id XOR username NOT NULL".
--     - Доставка обратно ТОЖЕ разная: Telegram-путь шлёт copyMessage в личку
--       клиента, веб-путь просто пишет строку в БД (personal chat не
--       существует) — код в bridge.py и так ветвится по каналу, общая
--       таблица не убирает эту ветку, только добавляет NULL-поля.
--     - Риск регрессии: tg_support_* уже покрыты test_bridge.py (14+
--       кейсов) и работают в проде (PR #2526) — трогать рабочую, протестированную
--       схему ради ещё не запущенной фичи повышает blast radius без выгоды.
--
--   Вариант Б (выбран) — новые web_support_threads/web_support_messages:
--     - Идентификатор клиента — username (X-Authenticated-User, сайт закрыт
--       Caddy basic_auth, публичного доступа нет — см. app/main.py rbac_guard)
--       — чистый, не smoke-и-зеркала не переиспользующий Telegram identity.
--     - topic_message_id-маршрутизация (ключевой механизм моста) СОХРАНЕНА
--       1-в-1 по конвенции 186: partial UNIQUE на topic_message_id,
--       заполняется только для direction='in', NULL для direction='out'.
--     - bridge.py меняется МИНИМАЛЬНО: _handle_group_reply получает одну
--       дополнительную ветку (резолвит tg-путь И web-путь, потом
--       existing orphan-warning) — существующий Telegram-путь не трогается.
--
-- ⚠️ CROSS-TABLE КОЛЛИЗИЯ topic_message_id (review M1, зафиксировано ДО
-- первого прод-использования, пока обе таблицы пусты):
--   Инвариант "topic_message_id уникален между tg_support_messages и
--   web_support_messages" на самом деле звучит так: "уникален, ПОКА
--   TELEGRAM_SUPPORT_CHAT_ID не менялся". Это OPS-инвариант, а НЕ DB-инвариант —
--   ничем не гарантирован. Смена/пересоздание support-группы обнуляет счётчик
--   Telegram message_id в новом чате; когда он дорастёт до диапазона,
--   использованного старым чатом, — number, ранее занятый ОДНОЙ таблицей,
--   может совпасть с числом, занятым ДРУГОЙ. Внутри одной таблицы partial
--   UNIQUE превращает такую коллизию в громкий отказ INSERT — это ок. МЕЖДУ
--   таблицами constraint'а нет: без доп. скоупинга бот молча доставил бы ответ
--   оператора НЕ ТОМУ клиенту (152-ФЗ-инцидент, происходящий тихо).
--   Фикс: колонка `support_chat_id` на web_support_messages (симметричная
--   колонка для УЖЕ применённой tg_support_messages — отдельная миграция
--   188_tg_support_chat_id_scope.sql, эту таблицу нельзя трогать здесь, она
--   уже применена/задеплоена как часть 186). Резолв (bridge.py) матчит ПАРУ
--   (support_chat_id, topic_message_id), а не topic_message_id в одиночку;
--   NULL (легаси-строки без этой колонки) — лениентный wildcard, т.к. на тот
--   момент действовал ровно один чат.
--
-- ЧТО:
--   - web_support_threads   — один тред на username (сайт = 1 логин = 1 линия
--                              переписки с поддержкой, без под-тредов).
--   - web_support_messages  — лог переписки, direction='in' (от юзера) |
--                              'out' (ответ оператора, реплай из bridge.py).
--
-- 152-ФЗ:
--   Переписка (text_body) — ПДн (может содержать любые данные, которые юзер
--   решит написать). ON DELETE CASCADE от web_support_threads делает erasure
--   ОДНОЙ операцией (DELETE FROM web_support_threads WHERE username = :u) ДЛЯ
--   КОПИИ В ЭТОЙ БД. Копия того же текста уже ушла в Telegram-топик (sendMessage
--   зеркало) и живёт ТАМ вне зоны действия этого DELETE — реальное "право на
--   забвение" по всей цепочке требует ОТДЕЛЬНОЙ процедуры (удаление сообщений в
--   Telegram-супергруппе через Bot API deleteMessage, вне scope этой миграции).
--   Не ссылаться на этот комментарий как на доказательство полного erasure.
--
-- IDEMPOTENCY: CREATE TABLE/INDEX IF NOT EXISTS — безопасный re-run.
-- Зависимости: нет (новые standalone таблицы, никакие существующие
-- tg_support_*/иные таблицы не трогаются — см. 188 для ALTER на tg_support_messages).

BEGIN;

CREATE TABLE IF NOT EXISTS web_support_threads (
    id            bigserial PRIMARY KEY,
    username      text NOT NULL UNIQUE,
    created_at    timestamptz NOT NULL DEFAULT now(),
    last_seen_at  timestamptz NOT NULL DEFAULT now(),
    last_read_at  timestamptz NOT NULL DEFAULT now()
);

COMMENT ON TABLE web_support_threads IS '152-ФЗ: одна строка на username (сайт МЕРА, X-Authenticated-User) — единый тред переписки с поддержкой через веб-чат. Удаление клиента — DELETE FROM web_support_threads WHERE username=...; ON DELETE CASCADE в web_support_messages подчищает переписку одной операцией.';
COMMENT ON COLUMN web_support_threads.username IS 'X-Authenticated-User (Caddy basic_auth) — сайт закрыт, анонимов нет, см. app/main.py rbac_guard.';
COMMENT ON COLUMN web_support_threads.last_seen_at IS 'Обновляется при отправке юзером нового сообщения (send-активность, НЕ на чтение истории).';
COMMENT ON COLUMN web_support_threads.last_read_at IS 'Отметка "прочитано до" (POST /api/v1/trade-in/support/read) — используется для счётчика непрочитанного (GET /support/unread).';

CREATE TABLE IF NOT EXISTS web_support_messages (
    id               bigserial PRIMARY KEY,
    thread_id        bigint NOT NULL REFERENCES web_support_threads (id) ON DELETE CASCADE,
    direction        text NOT NULL CHECK (direction IN ('in', 'out')),
    text_body        text NOT NULL CHECK (char_length(btrim(text_body)) > 0),
    topic_message_id bigint,
    support_chat_id  bigint,
    operator_tg_id   bigint,
    created_at       timestamptz NOT NULL DEFAULT now()
);

COMMENT ON TABLE web_support_messages IS '152-ФЗ: полный лог веб-чата поддержки (ПДн — содержимое сообщений; удаление подчищает КОПИЮ В ЭТОЙ БД, не Telegram-топик — см. блок 152-ФЗ выше). Каскадно удаляется вместе с web_support_threads по username.';
COMMENT ON COLUMN web_support_messages.direction IS '''in'' — сообщение от пользователя сайта; ''out'' — ответ оператора (доставлен через реплай в Telegram-топике, см. bridge.py _handle_group_reply).';
COMMENT ON COLUMN web_support_messages.text_body IS 'Текст сообщения. Веб-чат — текстовый MVP, медиа не поддерживается (в отличие от tg_support_messages.kind).';
COMMENT ON COLUMN web_support_messages.topic_message_id IS 'id зеркала (sendMessage) в support-топике — ключ маршрутизации ответа, только для direction=''in''. NULL для ''out'' (конвенция 186: маршрутизирующий ключ живёт исключительно на inbound-записи).';
COMMENT ON COLUMN web_support_messages.support_chat_id IS 'TELEGRAM_SUPPORT_CHAT_ID в момент отправки — скоупит резолв topic_message_id к ТЕКУЩЕЙ support-группе (review M1: без этого поля ротация группы даёт тихую cross-table коллизию, см. блок выше). NULL — лениентный wildcard для строк без этого поля.';
COMMENT ON COLUMN web_support_messages.operator_tg_id IS 'Telegram user id оператора, ответившего в топике; заполняется только для direction=''out''.';

CREATE UNIQUE INDEX IF NOT EXISTS web_support_messages_topic_message_id_uq
    ON web_support_messages (topic_message_id)
    WHERE topic_message_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS web_support_messages_thread_id_created_at_idx
    ON web_support_messages (thread_id, created_at DESC);

COMMIT;
