-- Упрощённый UPSERT для сохранения claim с известным claim_id
-- Используется в n8n workflow: form_get (нода claimsave)
-- Дата: 2025-11-21 (обновлено: сохранение существующих полей)
-- Описание: Простой INSERT/UPDATE для claim, т.к. claim_id уже известен
--           ВАЖНО: Сохраняет wizard_plan и другие поля из БД, если не пришли новые

-- Входные параметры:
-- $1: payload_partial_json (jsonb) - данные формы с wizard_answers, wizard_plan, documents_meta
-- $2: claim_id (text) - UUID заявки

WITH partial AS (
  SELECT 
    $1::jsonb AS p, 
    $2::text AS claim_id_str
),

-- Получаем существующий payload из БД (если запись есть)
-- ✅ ИСПРАВЛЕНО: Ищем и по ID, и по payload->>'claim_id', чтобы избежать дубликатов
existing_claim AS (
  SELECT 
    id,
    payload,
    created_at
  FROM clpr_claims
  WHERE id = (SELECT claim_id_str::uuid FROM partial)
     OR payload->>'claim_id' = (SELECT claim_id_str FROM partial)
  ORDER BY 
    CASE WHEN id = (SELECT claim_id_str::uuid FROM partial) THEN 1 ELSE 2 END,
    updated_at DESC
  LIMIT 1
),

-- Парсим wizard_answers
wizard_answers_parsed AS (
  SELECT 
    CASE 
      -- Из edit_fields_raw.body.wizard_answers (строка)
      WHEN partial.p->'edit_fields_raw'->'body'->>'wizard_answers' IS NOT NULL
        THEN (partial.p->'edit_fields_raw'->'body'->>'wizard_answers')::jsonb
      -- Из edit_fields_parsed.body.wizard_answers (строка)
      WHEN partial.p->'edit_fields_parsed'->'body'->>'wizard_answers' IS NOT NULL
        THEN (partial.p->'edit_fields_parsed'->'body'->>'wizard_answers')::jsonb
      -- Из корня как строка
      WHEN partial.p->>'wizard_answers' IS NOT NULL 
        THEN (partial.p->>'wizard_answers')::jsonb
      -- Из корня как объект
      WHEN partial.p->'wizard_answers' IS NOT NULL 
        AND jsonb_typeof(partial.p->'wizard_answers') = 'object'
        THEN partial.p->'wizard_answers'
      ELSE '{}'::jsonb
    END AS answers
  FROM partial
),

-- Парсим wizard_plan (или берём из существующей записи)
wizard_plan_parsed AS (
  SELECT 
    CASE 
      -- Сначала пытаемся взять из edit_fields_parsed.wizard_plan_parsed (уже объект)
      WHEN partial.p->'edit_fields_parsed'->'wizard_plan_parsed' IS NOT NULL
        AND jsonb_typeof(partial.p->'edit_fields_parsed'->'wizard_plan_parsed') = 'object'
        THEN partial.p->'edit_fields_parsed'->'wizard_plan_parsed'
      -- Или из корня как строка
      WHEN partial.p->>'wizard_plan' IS NOT NULL 
        THEN (partial.p->>'wizard_plan')::jsonb
      -- Или из корня как объект
      WHEN partial.p->'wizard_plan' IS NOT NULL 
        AND jsonb_typeof(partial.p->'wizard_plan') = 'object'
        THEN partial.p->'wizard_plan'
      -- Или из edit_fields_raw.body.wizard_plan (строка)
      WHEN partial.p->'edit_fields_raw'->'body'->>'wizard_plan' IS NOT NULL
        THEN (partial.p->'edit_fields_raw'->'body'->>'wizard_plan')::jsonb
      -- Если нет в payload - берём из существующей записи в БД
      WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'wizard_plan' IS NOT NULL)
        THEN (SELECT payload->'wizard_plan' FROM existing_claim)
      ELSE NULL
    END AS wizard_plan
  FROM partial
),

-- Парсим answers_prefill (или берём из БД)
answers_prefill_parsed AS (
  SELECT 
    CASE 
      WHEN partial.p->'answers_prefill' IS NOT NULL 
        AND jsonb_typeof(partial.p->'answers_prefill') = 'array'
        THEN partial.p->'answers_prefill'
      WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'answers_prefill' IS NOT NULL)
        THEN (SELECT payload->'answers_prefill' FROM existing_claim)
      ELSE '[]'::jsonb
    END AS answers_prefill
  FROM partial
),

-- Парсим coverage_report (или берём из БД)
coverage_report_parsed AS (
  SELECT 
    CASE 
      WHEN partial.p->'coverage_report' IS NOT NULL 
        AND jsonb_typeof(partial.p->'coverage_report') = 'object'
        THEN partial.p->'coverage_report'
      WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'coverage_report' IS NOT NULL)
        THEN (SELECT payload->'coverage_report' FROM existing_claim)
      ELSE NULL
    END AS coverage_report
  FROM partial
),

-- Парсим ai_agent1_facts (или берём из БД)
ai_agent1_facts_parsed AS (
  SELECT 
    CASE 
      WHEN partial.p->'ai_agent1_facts' IS NOT NULL 
        AND jsonb_typeof(partial.p->'ai_agent1_facts') = 'object'
        THEN partial.p->'ai_agent1_facts'
      WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'ai_agent1_facts' IS NOT NULL)
        THEN (SELECT payload->'ai_agent1_facts' FROM existing_claim)
      ELSE NULL
    END AS ai_agent1_facts
  FROM partial
),

-- Парсим ai_agent13_rag (или берём из БД)
ai_agent13_rag_parsed AS (
  SELECT 
    CASE 
      WHEN partial.p->'ai_agent13_rag' IS NOT NULL
        THEN partial.p->'ai_agent13_rag'
      WHEN partial.p->>'ai_agent13_rag' IS NOT NULL 
        THEN to_jsonb(partial.p->>'ai_agent13_rag')
      WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'ai_agent13_rag' IS NOT NULL)
        THEN (SELECT payload->'ai_agent13_rag' FROM existing_claim)
      ELSE NULL
    END AS ai_agent13_rag
  FROM partial
),

-- Парсим problem_description (или берём из БД)
problem_description_parsed AS (
  SELECT 
    CASE 
      WHEN partial.p->>'problem_description' IS NOT NULL 
        THEN partial.p->>'problem_description'
      WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->>'problem_description' IS NOT NULL)
        THEN (SELECT payload->>'problem_description' FROM existing_claim)
      ELSE NULL
    END AS problem_description
  FROM partial
),

-- UPSERT claim
-- ✅ ИСПРАВЛЕНО: Используем ID из existing_claim, если он найден, чтобы избежать дубликатов
claim_upsert AS (
  INSERT INTO clpr_claims (
    id,
    session_token,
    unified_id,
    contact_id,
    phone,
    channel,
    type_code,
    status_code,
    payload,
    created_at,
    updated_at,
    expires_at
  )
  SELECT 
    COALESCE(
      (SELECT id FROM existing_claim),
      partial.claim_id_str::uuid
    ),
    COALESCE(
      partial.p->>'session_id',
      partial.p->'edit_fields_parsed'->'body'->>'session_id',
      partial.p->'edit_fields_raw'->'body'->>'session_id',
      'sess-unknown'
    ),
    COALESCE(
      partial.p->>'unified_id',
      partial.p->'edit_fields_parsed'->'body'->>'unified_id',
      partial.p->'edit_fields_raw'->'body'->>'unified_id'
    ),
    COALESCE(
      partial.p->>'contact_id',
      partial.p->'edit_fields_parsed'->'body'->>'contact_id',
      partial.p->'edit_fields_raw'->'body'->>'contact_id'
    ),
    COALESCE(
      partial.p->>'phone',
      partial.p->'edit_fields_parsed'->'body'->>'phone',
      partial.p->'edit_fields_raw'->'body'->>'phone'
    ),
    'web_form',
    COALESCE(partial.p->>'type_code', 'consumer'),
    CASE
      WHEN (SELECT answers->>'docs_exist' FROM wizard_answers_parsed) = 'true'
        THEN 'in_work'
      ELSE 'draft'
    END,
    jsonb_build_object(
      'claim_id', partial.claim_id_str,
      'problem_description', (SELECT problem_description FROM problem_description_parsed),
      'answers', (SELECT answers FROM wizard_answers_parsed),
      'documents_meta', COALESCE(partial.p->'documents_meta', '[]'::jsonb),
      'wizard_plan', (SELECT wizard_plan FROM wizard_plan_parsed),
      'answers_prefill', (SELECT answers_prefill FROM answers_prefill_parsed),
      'coverage_report', (SELECT coverage_report FROM coverage_report_parsed),
      'ai_agent1_facts', (SELECT ai_agent1_facts FROM ai_agent1_facts_parsed),
      'ai_agent13_rag', (SELECT ai_agent13_rag FROM ai_agent13_rag_parsed),
      'phone', COALESCE(partial.p->>'phone', (SELECT payload->>'phone' FROM existing_claim)),
      'email', COALESCE(partial.p->>'email', (SELECT payload->>'email' FROM existing_claim))
    ),
    COALESCE(
      (SELECT created_at FROM existing_claim),
      now()
    ),
    now(),
    now() + interval '14 days'
  FROM partial
  ON CONFLICT (id) DO UPDATE SET
    session_token = EXCLUDED.session_token,
    unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id),
    contact_id = COALESCE(EXCLUDED.contact_id, clpr_claims.contact_id),
    phone = COALESCE(EXCLUDED.phone, clpr_claims.phone),
    status_code = EXCLUDED.status_code,
    payload = EXCLUDED.payload,
    updated_at = now(),
    expires_at = now() + interval '14 days'
  RETURNING id, status_code, payload, unified_id, contact_id, phone, session_token
),

-- UPSERT documents (если есть)
docs_upsert AS (
  INSERT INTO clpr_claim_documents (
    claim_id, 
    field_name, 
    file_id, 
    uploaded_at, 
    file_name, 
    original_file_name
  )
  SELECT
    partial.claim_id_str AS claim_id,
    doc.field_name,
    doc.file_id,
    COALESCE((doc.uploaded_at)::timestamptz, now()),
    doc.file_name,
    doc.original_file_name
  FROM partial
  CROSS JOIN LATERAL jsonb_to_recordset(
    COALESCE(partial.p->'documents_meta', '[]'::jsonb)
  ) AS doc(
    field_name text, 
    file_id text, 
    file_name text, 
    original_file_name text, 
    uploaded_at text
  )
  WHERE partial.p->'documents_meta' IS NOT NULL
    AND jsonb_array_length(partial.p->'documents_meta') > 0
  ON CONFLICT (claim_id, field_name) DO UPDATE SET
    file_id = EXCLUDED.file_id,
    uploaded_at = EXCLUDED.uploaded_at,
    file_name = EXCLUDED.file_name,
    original_file_name = EXCLUDED.original_file_name
  RETURNING id, claim_id, field_name, file_id, file_name, original_file_name
)

-- Возвращаем результат
SELECT
  (SELECT jsonb_build_object(
    'claim_id', cu.id::text,
    'claim_id_str', (cu.payload->>'claim_id'),
    'status_code', cu.status_code,
    'unified_id', cu.unified_id,
    'contact_id', cu.contact_id,
    'phone', cu.phone,
    'session_token', cu.session_token,
    'payload', cu.payload
  ) FROM claim_upsert cu) AS claim,
  
  (SELECT jsonb_agg(jsonb_build_object(
    'id', id, 
    'field_name', field_name, 
    'file_id', file_id,
    'file_name', file_name,
    'original_file_name', original_file_name
  )) FROM docs_upsert) AS documents;

/*
============================================================================
КЛЮЧЕВЫЕ ИЗМЕНЕНИЯ (2025-11-21):
============================================================================

1. Добавлена CTE "existing_claim" - читает существующий payload из БД
2. Все парсеры (wizard_plan, answers_prefill, coverage_report и т.д.) 
   теперь проверяют БД, если поле не пришло в payload_partial_json
3. Это критично для workflow form_get, где wizard_plan создаётся на Step 2,
   а файлы загружаются на Step 3 (без повторной отправки wizard_plan)

============================================================================
ИСПРАВЛЕНИЕ ДУБЛИКАТОВ (2025-11-24):
============================================================================

ПРОБЛЕМА: Создавались дубликаты записей с одинаковым claim_id, но разными ID
Причина: Разные SQL запросы использовали разные подходы:
  - Одни использовали claim_id как UUID для ID (partial.claim_id_str::uuid)
  - Другие искали по payload->>'claim_id' и создавали новый UUID, если не находили

РЕШЕНИЕ:
1. existing_claim теперь ищет и по ID, и по payload->>'claim_id':
   WHERE id = claim_id_str::uuid OR payload->>'claim_id' = claim_id_str
2. INSERT использует ID из existing_claim, если он найден:
   COALESCE((SELECT id FROM existing_claim), partial.claim_id_str::uuid)
3. Это гарантирует, что всегда используется одна и та же запись

============================================================================
ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
============================================================================

1. Первое сохранение (после Step 2 - описание проблемы):

SELECT * FROM ... WHERE ... = (
  '{
    "session_id": "sess_xxx",
    "unified_id": "usr_xxx",
    "contact_id": "12345",
    "phone": "79262306381",
    "problem_description": "Не вернули деньги...",
    "wizard_plan": {...полный wizard_plan...},
    "ai_agent1_facts": {...},
    "ai_agent13_rag": "...",
    "answers_prefill": [...],
    "coverage_report": {...}
  }'::jsonb,
  'uuid-here'::text
);

Результат: Создаётся claim с wizard_plan ✅

---

2. Обновление с файлами (Step 3 - загрузка документов):

SELECT * FROM ... WHERE ... = (
  '{
    "session_id": "sess_xxx",
    "unified_id": "usr_xxx",
    "wizard_answers": {"q1": "answer1"},
    "documents_meta": [
      {
        "field_name": "uploads[0][0]",
        "file_id": "clientright/0/file.pdf",
        "file_name": "file.pdf",
        "original_file_name": "original.pdf",
        "uploaded_at": "2025-11-21T12:00:00Z"
      }
    ]
  }'::jsonb,
  'СУЩЕСТВУЮЩИЙ-uuid'::text
);

Результат: 
- Обновляется answers ✅
- Добавляются documents_meta ✅
- wizard_plan СОХРАНЯЕТСЯ из БД ✅ (не затирается!)

---

3. Сохранение БЕЗ файлов (только answers):

SELECT * FROM ... WHERE ... = (
  '{
    "session_id": "sess_xxx",
    "wizard_answers": {"q1": "answer1"},
    "documents_meta": []
  }'::jsonb,
  'uuid-here'::text
);

Результат: 
- status_code = 'draft' (т.к. docs_exist != true)
- wizard_plan сохраняется из БД ✅

============================================================================
ОЖИДАЕМЫЙ РЕЗУЛЬТАТ:
============================================================================

{
  "claim": {
    "claim_id": "uuid",
    "claim_id_str": "uuid",
    "status_code": "draft" or "in_work",
    "unified_id": "usr_xxx",
    "contact_id": "12345",
    "phone": "79262306381",
    "session_token": "sess_xxx",
    "payload": {
      "claim_id": "uuid",
      "problem_description": "...",
      "answers": {...},
      "documents_meta": [...],
      "wizard_plan": {...},  // ← СОХРАНЯЕТСЯ из БД!
      "answers_prefill": [...],
      "coverage_report": {...},
      "ai_agent1_facts": {...},
      "ai_agent13_rag": "..."
    }
  },
  "documents": [...]
}

============================================================================
TROUBLESHOOTING:
============================================================================

Проблема: wizard_plan всё равно NULL после загрузки файлов
Причина: В n8n workflow form_get не передаётся claim_id в payload
Решение: Убедиться, что в set_token1 извлекается claim_id из webhook:
  "claim_id": "={{ $('Webhook').item.json.body.claim_id }}"

Проблема: Затираются ai_agent1_facts после Step 3
Причина: Не включены в payload при отправке
Решение: SQL сохраняет их из БД автоматически ✅

*/
