-- Migration 193: seed существующих юзеров в tradein_users + ASCII-CHECK на username
-- (issue #2557, эпик #2549)
--
-- WHY:
--   Migration 192 создала schema (tradein_users/tradein_sessions), но без данных —
--   DB-backed auth не может заработать, пока реальные аккаунты (сейчас живущие только
--   в auth/roles.yaml + caddy/users.caddy.snippet, legacy Caddy basic-auth) не отражены
--   в таблице. Эта миграция переносит org-карту, утверждённую владельцем продукта,
--   в данные — без единого пароля (см. WHAT.2) и без Python-кода (backend wiring — #2556,
--   team-UI для проставления паролей — отдельная задача, тоже #2556).
--
--   ASCII-CHECK (deep-review #2561, обязательное требование ДО прод-данных):
--   rbac кодирует session-username через `encode("latin-1", "replace")`. Кириллические
--   логины ОДИНАКОВОЙ длины схлопываются в одну и ту же byte-строку под этой кодировкой
--   ("иванов" и "петров" оба 6 кириллических символов -> оба превращаются в одинаковую
--   строку из '?' одной длины) -> общий downstream-identity между разными людьми, общая
--   квота, взаимный IDOR (один видит сессии/данные другого). Все текущие org-логины уже
--   ASCII (admin/kopylov/praktika/userN), поэтому constraint не конфликтует с seed'ом
--   ниже; он существует, чтобы navsegda запретить будущим кириллическим логинам попасть
--   в таблицу — fail-closed на уровне схемы, а не на уровне доверия к тому, что кто-то
--   не забудет проверить в UI/API layer.
--
-- WHAT:
--   1. ASCII-CHECK: tradein_users_username_ascii_ck CHECK (username ~ '^[A-Za-z0-9._-]{3,64}$').
--      Добавлен ДО seed-инсертов (порядок важен: constraint должен успеть провалидировать
--      вставляемые строки, а не просто существовать после них).
--   2. Seed — org-карта, утверждённая владельцем продукта (2026-07-30):
--        admin              role=admin,    manager_id=NULL, is_active=true  (владелец)
--        kopylov            role=manager,  manager_id=NULL, is_active=true  (отдельный клиент)
--        praktika           role=manager,  manager_id=NULL, is_active=true  (ГК «Практика»)
--        user1, user3-10    role=employee, manager_id=NULL, is_active=true  (свободные слоты, без org)
--        user2              role=employee, manager_id=NULL, is_active=false («Брусника», доступ
--                                                                             закрыт 2026-07-30)
--      password_hash = NULL для ВСЕХ — пароли админ проставит вручную через team-UI (#2556).
--      NULL-hash делает password-логин невозможным для этой строки, но НЕ снимает доступ:
--      в переходный период работает только legacy Caddy basic-auth fallback (dual-mode,
--      см. комментарий password_hash в 192_tradein_users_auth.sql) — никто не теряет доступ
--      из-за этой миграции.
--      display_name = 'Копылов' для kopylov (источник — auth.py::_USERNAME_PROFILE, уже
--      задокументированная фамилия). Для остальных — NULL, реальных данных нет, не выдумываем.
--      НЕ мигрируем admintest/pilottest/analysttest/expiredtest — temp QA-фикстуры
--      (auth/roles.yaml), остаются только там, в DB-backed auth не нужны.
--
-- IDEMPOTENCY:
--   - ADD CONSTRAINT через DO-блок с проверкой pg_constraint (Postgres не поддерживает
--     `ADD CONSTRAINT IF NOT EXISTS` для CHECK) — паттерн из
--     189_account_estimate_usage_nonnegative.sql.
--   - INSERT ... ON CONFLICT (username) DO UPDATE, но НЕ безусловно: password_hash,
--     is_active, display_name, org_name, email защищены COALESCE(текущее, EXCLUDED) —
--     если админ уже проставил пароль / поменял is_active / display_name вручную через
--     team-UI (#2556) между двумя прогонами этого файла (например ручной re-apply при
--     recovery — обычный auto-apply тречит filename в _schema_migrations и не запускает
--     файл дважды на одном окружении, но scratch/staging БД такого трекинга не имеют),
--     повторный прогон НЕ должен затереть это состояние NULL-ом / seed-дефолтом.
--     role/manager_id намеренно синкаются с EXCLUDED (не защищены) — это и есть источник
--     истины org-карты из этой миграции; если владелец продукта поправит эту таблицу
--     новой миграцией поверх, DO UPDATE-ветка должна донести исправление, а не заморозить
--     первый прогон навсегда.
--
-- Dependencies: 192_tradein_users_auth.sql (создаёт tradein_users, tradein_sessions).
-- Deploy order: после 192 (Foundation). Backend auth-код (login/password-verify) и
--   team-UI (#2556) — отдельные PR'ы ПОСЛЕ этой миграции (SQL-схема+данные первыми, см.
--   .claude/rules/sql.md "Migration order").

BEGIN;

-- Часть 1: ASCII-CHECK — должен применяться ДО вставки seed-строк ниже.
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint
        WHERE conname = 'tradein_users_username_ascii_ck'
    ) THEN
        ALTER TABLE tradein_users
            ADD CONSTRAINT tradein_users_username_ascii_ck
            CHECK (username ~ '^[A-Za-z0-9._-]{3,64}$');
    END IF;
END $$;

COMMENT ON CONSTRAINT tradein_users_username_ascii_ck ON tradein_users IS
    'Fail-closed защита от кириллических/не-ASCII логинов (deep-review #2561): '
    'rbac кодирует session-username через encode("latin-1","replace"), не-ASCII '
    'логины одинаковой длины схлопываются в общий downstream-identity (IDOR).';

-- Часть 2: seed org-карты (владелец продукта, 2026-07-30).
INSERT INTO tradein_users
    (username, password_hash, role, manager_id, display_name, org_name, email, is_active)
VALUES
    ('admin',    NULL, 'admin',    NULL, NULL,        NULL, NULL, true),
    ('kopylov',  NULL, 'manager',  NULL, 'Копылов',   NULL, NULL, true),
    ('praktika', NULL, 'manager',  NULL, NULL,        NULL, NULL, true),
    ('user1',    NULL, 'employee', NULL, NULL,        NULL, NULL, true),
    ('user2',    NULL, 'employee', NULL, NULL,        NULL, NULL, false),
    ('user3',    NULL, 'employee', NULL, NULL,        NULL, NULL, true),
    ('user4',    NULL, 'employee', NULL, NULL,        NULL, NULL, true),
    ('user5',    NULL, 'employee', NULL, NULL,        NULL, NULL, true),
    ('user6',    NULL, 'employee', NULL, NULL,        NULL, NULL, true),
    ('user7',    NULL, 'employee', NULL, NULL,        NULL, NULL, true),
    ('user8',    NULL, 'employee', NULL, NULL,        NULL, NULL, true),
    ('user9',    NULL, 'employee', NULL, NULL,        NULL, NULL, true),
    ('user10',   NULL, 'employee', NULL, NULL,        NULL, NULL, true)
ON CONFLICT (username) DO UPDATE SET
    role         = EXCLUDED.role,
    manager_id   = EXCLUDED.manager_id,
    password_hash = COALESCE(tradein_users.password_hash, EXCLUDED.password_hash),
    is_active    = COALESCE(tradein_users.is_active, EXCLUDED.is_active),
    display_name = COALESCE(tradein_users.display_name, EXCLUDED.display_name),
    org_name     = COALESCE(tradein_users.org_name, EXCLUDED.org_name),
    email        = COALESCE(tradein_users.email, EXCLUDED.email),
    updated_at   = now();

COMMIT;
