name: Lead region — prod ops

# Самодостаточный launch-инструмент фичи lead-region-resolution.
# Один воркфлоу, переключатель op. НЕ трогает deploy.yml / artisan-run.yml.
#
# op:
#   pre-migrate    — пред-применить миграцию 2026_05_31_100000 через postgres
#                    superuser (crm_app_user не член crm_migrator → обычный migrate
#                    падает) + пометить применённой, чтобы deploy её пропустил.
#   set-env        — записать DADATA-ключи (из secrets) + LEAD_REGION_RESOLVER_ENABLED
#                    (input flag) в боевой .env, перекэшировать config, рестарт очереди.
#   fetch-rossvyaz — скачать файл/архив реестра (input url) на прод в /var/www/liderra/rossvyaz.
#   import         — phone-ranges:import (input dry_run) под www-data (DDL-свап идёт
#                    через pgsql_supplier = crm_supplier_worker, член crm_migrator).
#   smoke          — phone-region:smoke --phone=<input phone> под www-data (нужны ключи).
#
# Secrets: LIDERRA_SSH_KEY, DADATA_API_KEY, DADATA_SECRET.

on:
  workflow_dispatch:
    inputs:
      op:
        description: 'Операция'
        required: true
        type: choice
        options:
          - pre-migrate
          - set-env
          - fetch-rossvyaz
          - fetch-via-runner
          - deliver-from-repo
          - import
          - smoke
      flag:
        description: 'set-env: LEAD_REGION_RESOLVER_ENABLED'
        required: false
        default: 'false'
        type: choice
        options:
          - 'false'
          - 'true'
      url:
        description: 'fetch-rossvyaz: прямая ссылка на CSV/ZIP реестра Россвязи'
        required: false
        type: string
      dir:
        description: 'import: каталог с CSV на проде'
        required: false
        default: '/var/www/liderra/rossvyaz'
        type: string
      dry_run:
        description: 'import: только staging без swap'
        required: false
        default: true
        type: boolean
      phone:
        description: 'smoke: телефон'
        required: false
        default: '79161234567'
        type: string

jobs:
  op:
    name: ${{ github.event.inputs.op }}
    runs-on: ubuntu-latest
    timeout-minutes: 15
    concurrency:
      group: liderra-prod-deploy
      cancel-in-progress: false

    env:
      LIDERRA_HOST: 111.88.246.137
      LIDERRA_USER: ubuntu
      APP_DIR: /var/www/liderra/app
      OP: ${{ github.event.inputs.op }}
      FLAG: ${{ github.event.inputs.flag }}
      URL: ${{ github.event.inputs.url }}
      DIR: ${{ github.event.inputs.dir }}
      DRY: ${{ github.event.inputs.dry_run }}
      PHONE: ${{ github.event.inputs.phone }}

    steps:
      - name: Setup SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.LIDERRA_SSH_KEY }}" > ~/.ssh/liderra_deploy
          chmod 600 ~/.ssh/liderra_deploy
          ssh-keyscan -H "${LIDERRA_HOST}" >> ~/.ssh/known_hosts 2>/dev/null

      - name: Checkout repo (for deliver-from-repo)
        if: ${{ github.event.inputs.op == 'deliver-from-repo' }}
        uses: actions/checkout@v4

      - name: op=pre-migrate (superuser DDL + mark applied)
        if: ${{ github.event.inputs.op == 'pre-migrate' }}
        run: |
          SQL_B64=$(cat <<'SQLEOF' | base64 -w0
          BEGIN;
          -- 1. phone_ranges_imports (FK target — создаём первым)
          CREATE TABLE phone_ranges_imports (
              id              BIGSERIAL PRIMARY KEY,
              imported_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
              source_url      TEXT NOT NULL,
              rows_inserted   INTEGER NOT NULL DEFAULT 0,
              rows_updated    INTEGER NOT NULL DEFAULT 0,
              checksum_sha256 TEXT NOT NULL,
              status          TEXT NOT NULL DEFAULT 'in_progress'
                  CHECK (status IN ('in_progress','completed','failed','rolled_back')),
              error           TEXT,
              completed_at    TIMESTAMPTZ
          );
          COMMENT ON TABLE phone_ranges_imports IS
              'Журнал импортов реестра Россвязи (idempotency по checksum_sha256, atomic-swap откат).';

          -- 2. phone_ranges (реестр диапазонов; SaaS-level, без RLS — публичные данные)
          CREATE TABLE phone_ranges (
              id                BIGSERIAL PRIMARY KEY,
              def_code          SMALLINT NOT NULL,
              from_num          BIGINT NOT NULL,
              to_num            BIGINT NOT NULL,
              operator          TEXT NOT NULL,
              region            TEXT NOT NULL,
              region_normalized TEXT,
              subject_code      SMALLINT,
              imported_at       TIMESTAMPTZ NOT NULL,
              import_id         BIGINT NOT NULL REFERENCES phone_ranges_imports(id),
              CONSTRAINT chk_phone_ranges_def_code CHECK (def_code BETWEEN 300 AND 999),
              CONSTRAINT chk_phone_ranges_subject_code CHECK (subject_code IS NULL OR subject_code BETWEEN 1 AND 89),
              CONSTRAINT chk_phone_ranges_range_valid CHECK (from_num <= to_num)
          );
          CREATE INDEX idx_phone_ranges_lookup ON phone_ranges (def_code, from_num, to_num);
          COMMENT ON TABLE phone_ranges IS
              'Реестр диапазонов нумерации Россвязи (rossvyaz.gov.ru). Локальный fallback для LeadRegionResolver.';

          GRANT SELECT ON phone_ranges, phone_ranges_imports TO crm_app_user, crm_supplier_worker;

          -- 3. lead_region_resolution_log (SaaS-level, партиционирован по received_at)
          CREATE TABLE lead_region_resolution_log (
              id                       BIGSERIAL,
              supplier_lead_id         BIGINT NOT NULL,
              received_at              TIMESTAMPTZ NOT NULL,
              phone_masked             TEXT NOT NULL,
              subject_code_resolved    SMALLINT,
              subject_code_from_tag    SMALLINT,
              region_source            TEXT NOT NULL
                  CHECK (region_source IN ('dadata','rossvyaz','tag','unknown')),
              dadata_qc                SMALLINT,
              dadata_provider          TEXT,
              dadata_type              TEXT,
              dadata_response_masked   JSONB,
              rossvyaz_matched         BOOLEAN NOT NULL DEFAULT FALSE,
              actual_subject_code      SMALLINT
                  CHECK (actual_subject_code IS NULL OR actual_subject_code BETWEEN 1 AND 89),
              substituted_subject_code SMALLINT
                  CHECK (substituted_subject_code IS NULL OR substituted_subject_code BETWEEN 1 AND 89),
              routing_step             SMALLINT
                  CHECK (routing_step IS NULL OR routing_step BETWEEN 1 AND 3),
              phone_operator           TEXT,
              cache_hit                BOOLEAN NOT NULL DEFAULT FALSE,
              duration_ms              INTEGER,
              resolved_at              TIMESTAMPTZ NOT NULL DEFAULT NOW(),
              PRIMARY KEY (id, received_at)
          ) PARTITION BY RANGE (received_at);

          CREATE INDEX idx_lrrl_lead_id ON lead_region_resolution_log (supplier_lead_id);
          CREATE INDEX idx_lrrl_source ON lead_region_resolution_log (region_source, received_at);
          COMMENT ON TABLE lead_region_resolution_log IS
              'Аудит каждого резолва региона лида (источник, qc, оператор, шаг каскада). Партиции помесячно.';

          GRANT SELECT, INSERT ON lead_region_resolution_log TO crm_supplier_worker;
          GRANT SELECT ON lead_region_resolution_log TO crm_app_user;

          CREATE TABLE lead_region_resolution_log_y2026_m05
              PARTITION OF lead_region_resolution_log
              FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
          CREATE TABLE lead_region_resolution_log_y2026_m06
              PARTITION OF lead_region_resolution_log
              FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');

          -- 4. supplier_leads: +4 колонки
          ALTER TABLE supplier_leads
              ADD COLUMN resolved_subject_code SMALLINT
                  CHECK (resolved_subject_code IS NULL OR resolved_subject_code BETWEEN 1 AND 89),
              ADD COLUMN region_source TEXT
                  CHECK (region_source IN ('dadata','rossvyaz','tag','unknown')),
              ADD COLUMN dadata_qc SMALLINT,
              ADD COLUMN phone_operator TEXT;

          -- 5. deals: +2 колонки
          ALTER TABLE deals
              ADD COLUMN phone_operator TEXT,
              ADD COLUMN region_substituted BOOLEAN NOT NULL DEFAULT FALSE;

          -- ownership как у миграции (она шла бы под crm_migrator)
          ALTER TABLE phone_ranges_imports OWNER TO crm_migrator;
          ALTER TABLE phone_ranges OWNER TO crm_migrator;
          ALTER TABLE lead_region_resolution_log OWNER TO crm_migrator;
          ALTER TABLE lead_region_resolution_log_y2026_m05 OWNER TO crm_migrator;
          ALTER TABLE lead_region_resolution_log_y2026_m06 OWNER TO crm_migrator;

          -- retention (system_settings, 12 мес)
          INSERT INTO system_settings (key, value, type, description, updated_at)
              SELECT 'partition_retention_months_lead_region_resolution_log', '12', 'int',
                     'Retention в месяцах для lead_region_resolution_log (~365 дней)', NOW()
              WHERE NOT EXISTS (
                  SELECT 1 FROM system_settings
                  WHERE key = 'partition_retention_months_lead_region_resolution_log');
          COMMIT;
          SQLEOF
          )
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" "SQL_B64='$SQL_B64' bash -s" <<'REMOTE' | tee /tmp/op.log
            set -euo pipefail
            MIG_NAME='2026_05_31_100000_create_phone_ranges_and_resolution_log'
            ALREADY=$(sudo -u postgres psql -d liderra -tAc "SELECT 1 FROM migrations WHERE migration='${MIG_NAME}' LIMIT 1")
            if [ "${ALREADY}" = "1" ]; then
              echo "Migration ${MIG_NAME} уже применена — пропускаю."
              exit 0
            fi
            TABLE_EXISTS=$(sudo -u postgres psql -d liderra -tAc "SELECT 1 FROM information_schema.tables WHERE table_name='phone_ranges' LIMIT 1")
            if [ "${TABLE_EXISTS}" != "1" ]; then
              echo "Применяю lead-region DDL через postgres superuser..."
              echo "$SQL_B64" | base64 -d | sudo -u postgres psql -d liderra -v ON_ERROR_STOP=1
            else
              echo "Таблица phone_ranges уже существует — только помечаю миграцию."
            fi
            NEXT_BATCH=$(sudo -u postgres psql -d liderra -tAc "SELECT COALESCE(MAX(batch),0)+1 FROM migrations")
            sudo -u postgres psql -d liderra -c \
              "INSERT INTO migrations (migration, batch) SELECT '${MIG_NAME}', ${NEXT_BATCH} WHERE NOT EXISTS (SELECT 1 FROM migrations WHERE migration='${MIG_NAME}')"
            echo "Помечено ${MIG_NAME} применённой (batch ${NEXT_BATCH})."
            echo "=== Проверка таблиц ==="
            sudo -u postgres psql -d liderra -c "\dt phone_ranges|phone_ranges_imports|lead_region_resolution_log" || true
          REMOTE

      - name: op=set-env (keys from secrets + flag → prod .env)
        if: ${{ github.event.inputs.op == 'set-env' }}
        env:
          DK: ${{ secrets.DADATA_API_KEY }}
          DS: ${{ secrets.DADATA_SECRET }}
        run: |
          DK_B64=$(printf '%s' "$DK" | base64 -w0)
          DS_B64=$(printf '%s' "$DS" | base64 -w0)
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" \
            "DK_B64='$DK_B64' DS_B64='$DS_B64' FLAG='$FLAG' APP_DIR='$APP_DIR' bash -s" <<'REMOTE' | tee /tmp/op.log
            set -euo pipefail
            ENV="${APP_DIR}/.env"
            DK=$(echo "$DK_B64" | base64 -d)
            DS=$(echo "$DS_B64" | base64 -d)
            upsert() {
              local key="$1" val="$2"
              sudo sed -i "/^${key}=/d" "$ENV"
              echo "${key}=${val}" | sudo tee -a "$ENV" >/dev/null
            }
            upsert DADATA_API_KEY "$DK"
            upsert DADATA_SECRET "$DS"
            upsert LEAD_REGION_RESOLVER_ENABLED "$FLAG"
            cd "$APP_DIR"
            sudo -u www-data php artisan config:clear
            sudo -u www-data php artisan config:cache
            sudo systemctl restart liderra-queue
            echo "set-env готово: flag=${FLAG}, ключи записаны."
            echo "=== Проверка (значения скрыты) ==="
            sudo grep -E '^(DADATA_API_KEY|DADATA_SECRET|LEAD_REGION_RESOLVER_ENABLED)=' "$ENV" | sed -E 's/=(.).*/=\1***/'
            echo "=== queue status ==="
            systemctl is-active liderra-queue || true
          REMOTE

      - name: op=fetch-rossvyaz (download registry on prod)
        if: ${{ github.event.inputs.op == 'fetch-rossvyaz' }}
        run: |
          # Пустой url → качаем все 4 официальных файла Минцифры за один прогон.
          # Непустой url → качаем только его (ручной режим).
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" \
            "URL='$URL' bash -s" <<'REMOTE' | tee /tmp/op.log
            set -euo pipefail
            DEST=/var/www/liderra/rossvyaz
            sudo mkdir -p "$DEST"
            cd "$DEST"
            if [ -n "$URL" ]; then
              URLS="$URL"
            else
              URLS="https://opendata.digital.gov.ru/downloads/DEF-9xx.csv
          https://opendata.digital.gov.ru/downloads/ABC-3xx.csv
          https://opendata.digital.gov.ru/downloads/ABC-4xx.csv
          https://opendata.digital.gov.ru/downloads/ABC-8xx.csv"
            fi
            for U in $URLS; do
              FNAME=$(basename "${U%%\?*}")
              [ -n "$FNAME" ] || FNAME="rossvyaz-download"
              echo "Скачиваю $U -> $FNAME"
              sudo curl -fSL --retry 3 --retry-delay 2 -e 'https://opendata.digital.gov.ru/registry/numeric/downloads/' -H 'Accept: text/csv,application/csv,application/octet-stream,*/*' -H 'Accept-Language: ru-RU,ru;q=0.9' -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' -o "$FNAME" "$U"
              case "$FNAME" in
                *.zip|*.ZIP) echo "Распаковываю zip..."; sudo unzip -o "$FNAME" ;;
              esac
            done
            sudo chown -R www-data:www-data "$DEST"
            echo "=== Содержимое $DEST ==="
            ls -lh "$DEST"
            FIRST_CSV=$(ls "$DEST"/DEF-9xx.csv "$DEST"/*.csv "$DEST"/*.CSV 2>/dev/null | head -1 || true)
            if [ -n "$FIRST_CSV" ]; then
              echo "=== Первые строки $FIRST_CSV (cp1251→utf8) ==="
              sudo head -3 "$FIRST_CSV" | iconv -f cp1251 -t utf-8 2>/dev/null || sudo head -3 "$FIRST_CSV"
            fi
          REMOTE

      - name: op=fetch-via-runner (download on runner, ship to prod)
        if: ${{ github.event.inputs.op == 'fetch-via-runner' }}
        run: |
          mkdir -p /tmp/rv && cd /tmp/rv && rm -f /tmp/rv/*.csv
          for U in https://opendata.digital.gov.ru/downloads/DEF-9xx.csv https://opendata.digital.gov.ru/downloads/ABC-3xx.csv https://opendata.digital.gov.ru/downloads/ABC-4xx.csv https://opendata.digital.gov.ru/downloads/ABC-8xx.csv; do
            FN=$(basename "${U%%\?*}")
            echo "runner: скачиваю $U -> $FN"
            curl -fSL --retry 3 --retry-delay 2 -e 'https://opendata.digital.gov.ru/registry/numeric/downloads/' -H 'Accept: text/csv,application/csv,*/*' -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' -o "$FN" "$U"
          done
          echo "=== скачано на runner ==="
          ls -lh /tmp/rv | tee /tmp/op.log
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" 'mkdir -p /tmp/rvup && rm -f /tmp/rvup/*.csv'
          scp -i ~/.ssh/liderra_deploy /tmp/rv/*.csv "${LIDERRA_USER}@${LIDERRA_HOST}:/tmp/rvup/"
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" 'sudo mkdir -p /var/www/liderra/rossvyaz && sudo mv /tmp/rvup/*.csv /var/www/liderra/rossvyaz/ && sudo chown -R www-data:www-data /var/www/liderra/rossvyaz && echo "=== на проде /var/www/liderra/rossvyaz ===" && ls -lh /var/www/liderra/rossvyaz' | tee -a /tmp/op.log

      - name: op=deliver-from-repo (scp repo CSV/ZIP to prod, unzip there)
        if: ${{ github.event.inputs.op == 'deliver-from-repo' }}
        run: |
          # Ищем файлы реестра где угодно (корень или папка), .csv или .zip
          mapfile -t FILES < <(find . -maxdepth 2 -type f \( \( -iname 'DEF-9xx*' -o -iname 'ABC-3xx*' -o -iname 'ABC-4xx*' -o -iname 'ABC-8xx*' \) -iname '*.csv' -o -iname '*.zip' \) ! -path './.git/*')
          if [ ${#FILES[@]} -eq 0 ]; then
            echo "::error::Не нашёл файлов реестра (DEF-9xx/ABC-*.csv|zip) ни в корне, ни в rossvyaz-data/. Проверь, что они закоммичены в репозиторий."; exit 1
          fi
          echo "=== файлы в репозитории (rossvyaz-data/) ==="
          ls -lh "${FILES[@]}" | tee /tmp/op.log
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" 'mkdir -p /tmp/rvup && rm -f /tmp/rvup/*'
          scp -i ~/.ssh/liderra_deploy "${FILES[@]}" "${LIDERRA_USER}@${LIDERRA_HOST}:/tmp/rvup/"
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" '
            cd /tmp/rvup
            for z in *.zip *.ZIP; do if [ -e "$z" ]; then echo "распаковываю $z"; unzip -o "$z"; rm -f "$z"; fi; done
            sudo mkdir -p /var/www/liderra/rossvyaz
            find . -iname "*.csv" -exec sudo mv {} /var/www/liderra/rossvyaz/ \;
            sudo chown -R www-data:www-data /var/www/liderra/rossvyaz
            echo "=== на проде /var/www/liderra/rossvyaz ==="
            ls -lh /var/www/liderra/rossvyaz
          ' | tee -a /tmp/op.log

      - name: op=import (phone-ranges:import)
        if: ${{ github.event.inputs.op == 'import' }}
        run: |
          DRY_FLAG=""
          if [ "${DRY}" = "true" ]; then DRY_FLAG="--dry-run"; fi
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" \
            "APP_DIR='$APP_DIR' DIR='$DIR' DRY_FLAG='$DRY_FLAG' bash -s" <<'REMOTE' | tee /tmp/op.log
            set -e
            cd "$APP_DIR"
            echo "=== phone-ranges:import --dir=${DIR} ${DRY_FLAG} ==="
            sudo -u www-data php artisan phone-ranges:import --dir="$DIR" $DRY_FLAG 2>&1
            echo "=== Счётчики ==="
            sudo -u postgres psql -d liderra -c "SELECT count(*) AS phone_ranges FROM phone_ranges" 2>&1 || true
            # staging-счётчик: 2 отдельных запроса, чтобы Postgres не парсил
            # подзапрос к phone_ranges_staging, когда таблица уже свапнута (иначе
            # ERROR relation "phone_ranges_staging" does not exist даже в ветке CASE).
            STAGING_EXISTS=$(sudo -u postgres psql -d liderra -tAc "SELECT to_regclass('phone_ranges_staging') IS NOT NULL")
            if [ "$STAGING_EXISTS" = "t" ]; then
              sudo -u postgres psql -d liderra -c "SELECT count(*) AS staging_rows FROM phone_ranges_staging" 2>&1 || true
            else
              echo "staging: отсутствует (после свапа — норма)"
            fi
            echo "=== Последний импорт ==="
            sudo -u postgres psql -d liderra -c \
              "SELECT id, status, rows_inserted, rows_updated, imported_at FROM phone_ranges_imports ORDER BY id DESC LIMIT 3" 2>&1 || true
          REMOTE

      - name: op=smoke (phone-region:smoke)
        if: ${{ github.event.inputs.op == 'smoke' }}
        run: |
          ssh -i ~/.ssh/liderra_deploy "${LIDERRA_USER}@${LIDERRA_HOST}" \
            "APP_DIR='$APP_DIR' PHONE='$PHONE' bash -s" <<'REMOTE' | tee /tmp/op.log
            set -e
            cd "$APP_DIR"
            echo "=== phone-region:smoke --phone=${PHONE} ==="
            sudo -u www-data php artisan phone-region:smoke --phone="$PHONE" 2>&1
          REMOTE

      - name: Print summary
        if: always()
        run: |
          {
            echo "## lead-region-ops: \`${OP}\`"
            echo
            echo '```'
            cat /tmp/op.log 2>/dev/null || echo "(нет вывода)"
            echo '```'
          } >> "$GITHUB_STEP_SUMMARY"

      - name: Cleanup SSH key
        if: always()
        run: rm -f ~/.ssh/liderra_deploy
