<?php
/* ==========================================================
   Bitrix Fresh Lead Poller + Dynamic Reassign (Call Center)
   - ALL Fresh leads (no date filter)
   - First ping after 5 min, then every 5 min
   - Hidden copy of every ping to team leads group
   - On Ping #5: move to "New Leads" -> assign to random dynamic call-center user -> back to "Fresh Lead"
   - REASSIGN_POOL is built dynamically from Bitrix "user.search" (dept=5, ACTIVE=true, employee)
   ========================================================== */
date_default_timezone_set('Europe/Istanbul');

/* ---- Only allow CLI (cron) ---- */


/* ==== Bitrix ==== */
$domain = 'hosfinder-clinic.bitrix24.com.tr';
$userId = '13';
$token  = '0fp03wauuxz2v3gy';

/* ==== Stages ==== */
const STAGE_FRESH_ID        = 'UC_L5M9TL'; // Fresh Lead
const STAGE_NEW_NAME        = 'New Leads'; // will resolve ID by name
const STAGE_NEW_FALLBACK_ID = 'NEW';

/* ==== WhatsApp (Maytapi) ==== */
const MAY_BASE    = 'https://api.maytapi.com/api';
const MAY_PRODUCT = '396f5410-b88a-475a-888a-45e17f7e1850';
const MAY_PHONE   = '92917';
const MAY_KEY     = '2fcccc11-1f79-49a1-933c-1e0b93681f04';

/* Hidden copy for every ping */
const WA_TEAM_LEADS_GROUP = '120363405084370128@g.us';

/* Optional manual overrides: Bitrix user_id -> WA number (905...) */
$SELLER_WA = [
  // '13' => '905423212512',
];

/* ==== Call Center dynamic pool config ==== */
/* Dept IDs to include (add 9 etc. if needed): */
const CALL_CENTER_DEPTS = [5];
/* Cache to reduce API load (agents list is cached for 5 minutes) */
const AGENTS_CACHE_FILE = __DIR__ . '/data/agents_cache.json';
const AGENTS_CACHE_TTL  = 300; // seconds

/* ==== Ping policy (5 minutes) ==== */
const FIRST_GAP_SEC  = 300;
const PING_EVERY_SEC = 300;
const STOP_AFTER     = 5;  // at 5th ping run reassign flow
const ESCALATE_AT    = 1;  // from ping #1, send hidden copy to team leads

/* ==== Store (JSON) ==== */
const DATA_DIR  = __DIR__ . '/data';
const DATA_FILE = DATA_DIR . '/fresh_pings.json';

/* ==== Logging ==== */
error_reporting(E_ALL);
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/bitrix_fresh_poller_error.log');

/* ================= Helpers ================= */

if (!function_exists('str_starts_with')) {
  function str_starts_with($h,$n){ return $n==='' || strpos($h,$n)===0; }
}

/* IO */
function ensure_store(){
  if (!is_dir(DATA_DIR)) @mkdir(DATA_DIR, 0775, true);
  if (!file_exists(DATA_FILE)) file_put_contents(DATA_FILE, json_encode(new stdClass(), JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
  if (!is_dir(dirname(AGENTS_CACHE_FILE))) @mkdir(dirname(AGENTS_CACHE_FILE), 0775, true);
}
function load_store(){
  ensure_store();
  $h = fopen(DATA_FILE, 'r'); if(!$h) return [];
  flock($h, LOCK_SH); $raw = stream_get_contents($h); flock($h, LOCK_UN); fclose($h);
  $j = json_decode($raw, true);
  return is_array($j) ? $j : [];
}
function save_store($arr){
  ensure_store();
  $h = fopen(DATA_FILE, 'c+'); if(!$h) return false;
  flock($h, LOCK_EX); ftruncate($h, 0); rewind($h);
  fwrite($h, json_encode($arr, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
  fflush($h); flock($h, LOCK_UN); fclose($h);
  return true;
}

/* Bitrix REST (JSON) */
function b24_json($method,$params){
  global $domain,$userId,$token;
  $url = "https://{$domain}/rest/{$userId}/{$token}/{$method}.json";
  $ch  = curl_init($url);
  curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($params, JSON_UNESCAPED_UNICODE),
    CURLOPT_TIMEOUT        => 25
  ]);
  $out = curl_exec($ch);
  $code= curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);
  $j = json_decode($out, true);
  if ($code>=400 || isset($j['error'])) {
    error_log("B24 ERR $method ($code): ".substr($out,0,500));
  }
  return $j;
}
function b24_list_json($method,$params=[]){
  $start = 0; $all = [];
  do {
    $params['start'] = $start;
    $resp = b24_json($method,$params);
    if (!isset($resp['result'])) break;
    $all   = array_merge($all, $resp['result']);
    $start = isset($resp['next']) ? $resp['next'] : null;
  } while ($start !== null);
  return $all;
}

/* Status map (NAME -> STATUS_ID), ENTITY_ID=STATUS */
function b24_status_map(){
  static $map=null;
  if ($map!==null) return $map;
  $data = b24_json('crm.status.list', []);
  $map = [];
  if (!empty($data['result'])) {
    foreach ($data['result'] as $it) {
      if (($it['ENTITY_ID']??'')==='STATUS') {
        $name = trim((string)($it['NAME'] ?? ''));
        $id   = (string)($it['STATUS_ID'] ?? '');
        if ($name!=='' && $id!=='') $map[mb_strtolower($name,'UTF-8')] = $id;
      }
    }
  }
  return $map;
}
function get_new_stage_id(){
  $map = b24_status_map();
  $id  = $map[mb_strtolower(STAGE_NEW_NAME,'UTF-8')] ?? null;
  return $id ?: STAGE_NEW_FALLBACK_ID;
}

/* Users */
function b24_user_name($id){
  static $cache = [];
  $id = (string)$id;
  if (isset($cache[$id])) return $cache[$id];
  $resp = b24_json('user.get', ['ID'=>$id]);
  $name = "User #$id";
  if (!empty($resp['result'][0])) {
    $u = $resp['result'][0];
    $name = trim(($u['NAME'] ?? '').' '.($u['LAST_NAME'] ?? '')) ?: $name;
  }
  return $cache[$id] = $name;
}
function b24_user_phone($id){
  static $cache = [];
  $id = (string)$id;
  if (isset($cache[$id])) return $cache[$id];
  $resp = b24_json('user.get', ['ID'=>$id]);
  $num = '';
  if (!empty($resp['result'][0])) {
    $u = $resp['result'][0];
    foreach (['PERSONAL_MOBILE','WORK_PHONE','PERSONAL_PHONE'] as $k) {
      if (!empty($u[$k]) && is_string($u[$k])) { $num = $u[$k]; break; }
    }
    if ($num === '' && !empty($u['PHONES']) && is_array($u['PHONES'])) {
      foreach ($u['PHONES'] as $p) { if (!empty($p['VALUE'])) { $num = $p['VALUE']; break; } }
    }
  }
  return $cache[$id] = $num;
}

/* Lead/contact phone extraction */
function first_value_from_multifield($arr){
  if (!is_array($arr)) return '';
  $prio = ['MOBILE','WORK','HOME','OTHER','FAX'];
  $byType = [];
  foreach ($arr as $it) {
    $val = trim((string)($it['VALUE'] ?? ''));
    $typ = strtoupper((string)($it['VALUE_TYPE'] ?? ($it['TYPE_ID'] ?? '')));
    if ($val === '') continue;
    $byType[$typ] = $val;
  }
  foreach ($prio as $t) if (!empty($byType[$t])) return $byType[$t];
  foreach ($arr as $it) { $val = trim((string)($it['VALUE'] ?? '')); if ($val !== '') return $val; }
  return '';
}
function b24_lead_phone_raw($leadId, &$outContactId = null){
  $resp = b24_json('crm.lead.get', ['id'=>$leadId]);
  $outContactId = null;
  if (empty($resp['result'])) return '';
  $lead = $resp['result'];
  if (!empty($lead['PHONE'])) {
    $p = first_value_from_multifield($lead['PHONE']);
    if ($p !== '') return $p;
  }
  if (!empty($lead['CONTACT_ID'])) $outContactId = $lead['CONTACT_ID'];
  return '';
}
function b24_contact_phone_raw($contactId){
  $resp = b24_json('crm.contact.get', ['id'=>$contactId]);
  if (empty($resp['result'])) return '';
  $c = $resp['result'];
  if (!empty($c['PHONE'])) {
    $p = first_value_from_multifield($c['PHONE']);
    if ($p !== '') return $p;
  }
  return '';
}

/* WA utils */
function normalize_msisdn($s) {
  if (is_string($s) && stripos($s, '@g.us') !== false) return trim($s); // group id
  if (is_array($s)) {
    foreach ($s as $item) {
      if (is_string($item) && $item !== '') { $s = $item; break; }
      if (is_array($item) && !empty($item['VALUE'])) { $s = $item['VALUE']; break; }
    }
  }
  $s = trim((string)$s);
  if ($s === '') return '';
  if (stripos($s, '@g.us') !== false) return $s;
  $s = preg_replace('~https?://wa\.me/~i', '', $s);
  $s = str_replace([' ', '-'], '', $s);
  $s = preg_replace('/^\+/',  '', $s);
  $s = preg_replace('/^00/',  '', $s);
  $d = preg_replace('/\D+/',  '', $s);
  if ($d === '') return '';
  if (preg_match('/^90\d{10}$/', $d)) return $d;
  if (preg_match('/^0(\d{10})$/', $d, $m)) return '90'.$m[1];
  if (preg_match('/^5\d{9}$/',   $d)) return '90'.$d;
  return $d;
}
function wa_send($to, $text){
  $url  = MAY_BASE.'/'.MAY_PRODUCT.'/'.MAY_PHONE.'/sendMessage';
  $body = ['to_number'=>$to, 'type'=>'text', 'message'=>$text];
  $ch   = curl_init($url);
  curl_setopt_array($ch, [
    CURLOPT_POST=>true,
    CURLOPT_HTTPHEADER=>['Content-Type: application/json', 'x-maytapi-key: '.MAY_KEY],
    CURLOPT_POSTFIELDS=>json_encode($body),
    CURLOPT_RETURNTRANSFER=>true,
    CURLOPT_TIMEOUT=>15
  ]);
  $res  = curl_exec($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);
  if ($code < 200 || $code >= 300) error_log("WA ERR $code: ".$res);
  return $code >= 200 && $code < 300;
}

/* Pretty phone for message body */
function prettify_phone_for_msg($raw){
  $s = trim((string)$raw);
  if ($s === '') return '-';
  if (preg_match('~wa\.me/(\d+)~i', $s, $m)) return $m[1];
  return $s;
}

/* Ping texts (EN) */
function build_ping_msg($pingNo, $lead, $sinceTs, $agentName, $leadPhoneRaw){
  $leadId   = $lead['ID'] ?? '-';
  $title    = trim((string)($lead['TITLE'] ?? ''));
  $leadT    = ($title !== '') ? $title : '-';
  $agentId  = (string)($lead['ASSIGNED_BY_ID'] ?? '-');
  $agoMin   = max(0, (int)floor((time() - $sinceTs) / 60));
  $leadUrl  = "https://".htmlspecialchars($GLOBALS['domain'])."/crm/lead/details/".urlencode($leadId)."/";
  $leadTel  = prettify_phone_for_msg($leadPhoneRaw);

  $ctx = [
    '{LEAD_ID}'   => $leadId,
    '{LEAD_TITLE}'=> $leadT,
    '{AGENT_NAME}'=> $agentName,
    '{AGENT_ID}'  => $agentId,
    '{SINCE_MIN}' => $agoMin,
    '{LEAD_URL}'  => $leadUrl,
    '{LEAD_PHONE}'=> $leadTel,
  ];

  $templates = [
    1 => "⚠️ Attention: Make first contact within 5 minutes. (Ping #1)

Lead: #{LEAD_ID} — {LEAD_TITLE}
Customer phone: {LEAD_PHONE}
Assigned to: {AGENT_NAME} (ID {AGENT_ID})
Waiting since: {SINCE_MIN} min
CRM: {LEAD_URL}

Policy:
• The first alert is sent only to you.
• If no message is sent within 5 minutes, subsequent alerts will also notify team leads.
• If no contact is made by Ping #5, the lead will be reassigned to another sales consultant.

Please send a WhatsApp/SMS or call now and log the outreach in CRM.",
    2 => "⚠️ Ping #2 — Reminder: no first contact logged

Lead: #{LEAD_ID} — {LEAD_TITLE}
Customer phone: {LEAD_PHONE}
Assigned to: {AGENT_NAME} (ID {AGENT_ID})
Waiting since: {SINCE_MIN} min
CRM: {LEAD_URL}

Policy:
• From this alert onward, team leads are notified.
• If no outreach is made within 5 minutes, subsequent alerts will continue to include team leads.
• If no contact is made by Ping #5, the lead will be reassigned.

Immediate action is required.",
    3 => "🚨 Ping #3 — Serious warning: lead still waiting

Lead: #{LEAD_ID} — {LEAD_TITLE}
Customer phone: {LEAD_PHONE}
Assigned to: {AGENT_NAME} (ID {AGENT_ID})
Waiting since: {SINCE_MIN} min
CRM: {LEAD_URL}

Policy:
• Team leads have been notified since Ping #2.
• Alerts will continue until first contact is logged or the lead is reassigned at Ping #5.

Please contact the customer now and record it in CRM.",
    4 => "🚨 Ping #4 — Final warning before reassignment

Lead: #{LEAD_ID} — {LEAD_TITLE}
Customer phone: {LEAD_PHONE}
Assigned to: {AGENT_NAME} (ID {AGENT_ID})
Waiting since: {SINCE_MIN} min
CRM: {LEAD_URL}

Policy:
• Team leads are monitoring this delay.
• If first contact is not logged by the next alert, the lead will be reassigned (Ping #5).
• This delay will negatively impact your performance record.

Immediate outreach is mandatory.",
    5 => "⛔ Ping #5 — Final notice: reassignment in progress

Lead: #{LEAD_ID} — {LEAD_TITLE}
Customer phone: {LEAD_PHONE}
Assigned to: {AGENT_NAME} (ID {AGENT_ID})
Waiting since: {SINCE_MIN} min
CRM: {LEAD_URL}

Policy:
• Team leads have been notified.
• The lead is now being reassigned to another sales consultant.
• This event will be recorded as a critical performance issue.

You no longer own this lead."
  ];

  $tpl = $templates[$pingNo] ?? $templates[1];
  return strtr($tpl, $ctx);
}

/* ========= Dynamic call-center pool ========= */

function agents_cache_get(){
  if (is_file(AGENTS_CACHE_FILE)) {
    $age = time() - filemtime(AGENTS_CACHE_FILE);
    if ($age <= AGENTS_CACHE_TTL) {
      $raw = file_get_contents(AGENTS_CACHE_FILE);
      $j = json_decode($raw, true);
      if (is_array($j)) return $j;
    }
  }
  return null;
}
function agents_cache_put($arr){
  @file_put_contents(AGENTS_CACHE_FILE, json_encode($arr, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
}

/**
 * Fetch dynamic call center agents from Bitrix
 * Returns array of items: ['id' => '35', 'wa' => '9054...', 'has_phone' => true/false]
 */
function fetch_callcenter_agents(){
  global $SELLER_WA;

  // Try cache first
  $cache = agents_cache_get();
  if (is_array($cache)) return $cache;

  $results = [];
  foreach (CALL_CENTER_DEPTS as $deptId) {
    // Bitrix user.search with filters
    $items = b24_list_json('user.search', [
      'FILTER' => [
        'ACTIVE'      => true,
        'USER_TYPE'   => 'employee',
        'UF_DEPARTMENT'=> $deptId,
      ],
      'SELECT' => ['ID','NAME','LAST_NAME','PERSONAL_MOBILE','WORK_PHONE','PERSONAL_PHONE','USER_TYPE','ACTIVE','UF_DEPARTMENT']
    ]);
    foreach ($items as $u) {
      $id = (string)($u['ID'] ?? '');
      if ($id === '') continue;
      if (($u['ACTIVE'] ?? false) !== true) continue;
      if (($u['USER_TYPE'] ?? '') !== 'employee') continue;

      // override > personal_mobile > work_phone > personal_phone
      $rawPhone = $SELLER_WA[$id] ?? '';
      if ($rawPhone === '') {
        foreach (['PERSONAL_MOBILE','WORK_PHONE','PERSONAL_PHONE'] as $k) {
          if (!empty($u[$k]) && is_string($u[$k])) { $rawPhone = $u[$k]; break; }
        }
      }
      $wa = normalize_msisdn($rawPhone);
      $results[$id] = [
        'id' => $id,
        'wa' => $wa,
        'has_phone' => ($wa !== '')
      ];
    }
  }

  // De-dup & list
  $list = array_values($results);

  // Save cache
  agents_cache_put($list);

  return $list;
}

/**
 * Choose random agent, prefer ones with phone, exclude current
 */
function choose_random_agent($currentAssigned){
  $agents = fetch_callcenter_agents();
  if (empty($agents)) return null;

  $currentAssigned = (string)$currentAssigned;

  // Split by has_phone
  $withPhone = [];
  $without   = [];
  foreach ($agents as $a) {
    if ($a['id'] === $currentAssigned) continue; // exclude current owner
    if (!empty($a['has_phone'])) $withPhone[] = $a; else $without[] = $a;
  }
  $pool = !empty($withPhone) ? $withPhone : $without;
  if (empty($pool)) return null;

  return $pool[array_rand($pool)];
}

/* ======== Reassign after Ping #5 (dynamic) ======== */
function reassign_after_ping5($leadId, $currentAssigned){
  // 1) New Leads stage ID
  $newStage = get_new_stage_id();

  // 2) Move to NEW
  b24_json('crm.lead.update', ['id'=>$leadId, 'fields'=>['STATUS_ID'=>$newStage]]);

  // 3) Pick dynamic random agent (prefer with phone)
  $chosen = choose_random_agent($currentAssigned);
  if ($chosen) {
    b24_json('crm.lead.update', ['id'=>$leadId, 'fields'=>['ASSIGNED_BY_ID'=>$chosen['id']]]);
  } else {
    error_log("Reassign warning: no candidate found, keeping owner for lead #$leadId");
  }

  // 4) Back to FRESH
  b24_json('crm.lead.update', ['id'=>$leadId, 'fields'=>['STATUS_ID'=>STAGE_FRESH_ID]]);

  error_log("Reassigned lead #$leadId after Ping5 -> NEW -> random -> FRESH");
}

/* =================== MAIN =================== */

// Fetch ALL Fresh Leads
$filter = ['STATUS_ID' => STAGE_FRESH_ID];
$select = ['ID','TITLE','STATUS_ID','ASSIGNED_BY_ID','DATE_MODIFY','MOVED_TIME','CONTACT_ID'];
$leads  = b24_list_json('crm.lead.list', ['filter'=>$filter,'select'=>$select,'order'=>['ID'=>'DESC']]);

$store = load_store();
$now   = time();
$sent  = [];
$currentFreshIds = [];

foreach ($leads as $L) {
  $id         = (string)($L['ID'] ?? '');
  if ($id === '') continue;

  $currentFreshIds[] = $id;

  $statusId   = (string)($L['STATUS_ID']      ?? '');
  $assignedId = (string)($L['ASSIGNED_BY_ID'] ?? '');
  $modTs      = strtotime($L['DATE_MODIFY']   ?? '') ?: 0;
  $movedTs    = strtotime($L['MOVED_TIME']    ?? '') ?: 0;
  $sinceTs    = max($modTs, $movedTs);
  if ($sinceTs <= 0) $sinceTs = $now;

  // Init/reset record
  $rec = $store[$id] ?? [
    'lead_id'      => $id,
    'status_id'    => $statusId,
    'assigned_id'  => $assignedId,
    'since_ts'     => $sinceTs,
    'ping_count'   => 0,
    'next_ping_ts' => $sinceTs + FIRST_GAP_SEC
  ];
  if ($assignedId !== ($rec['assigned_id'] ?? '')
      || $statusId !== ($rec['status_id'] ?? '')
      || $sinceTs > ($rec['since_ts'] ?? 0)) {
    $rec['status_id']    = $statusId;
    $rec['assigned_id']  = $assignedId;
    $rec['since_ts']     = $sinceTs;
    $rec['ping_count']   = 0;
    $rec['next_ping_ts'] = $sinceTs + FIRST_GAP_SEC;
  }

  // Wait initial 5 minutes
  if ($now < $rec['since_ts'] + FIRST_GAP_SEC) { $store[$id] = $rec; continue; }
  // Next ping due?
  if ($now < (int)$rec['next_ping_ts'])        { $store[$id] = $rec; continue; }

  // Resolve agent target WA number
  $agentName     = b24_user_name($assignedId);
  $agentRawPhone = $GLOBALS['SELLER_WA'][$assignedId] ?? b24_user_phone($assignedId);
  $agentTo       = normalize_msisdn($agentRawPhone);
  if ($agentTo === '') $agentTo = WA_TEAM_LEADS_GROUP; // if empty, at least leaders see it

  // Lead phone
  $contactId = null;
  $leadPhoneRaw = b24_lead_phone_raw($id,$contactId);
  if ($leadPhoneRaw === '' && $contactId) $leadPhoneRaw = b24_contact_phone_raw($contactId);

  // Message
  $pingNo = $rec['ping_count'] + 1;
  $msg    = build_ping_msg($pingNo, $L, $rec['since_ts'], $agentName, $leadPhoneRaw);

  // Send
  wa_send($agentTo, $msg);                           // to agent (or leaders if agent phone missing)
  if ($pingNo >= ESCALATE_AT) wa_send(WA_TEAM_LEADS_GROUP, $msg); // hidden copy

  // Plan next
  $rec['ping_count']   = $pingNo;
  $rec['next_ping_ts'] = $now + PING_EVERY_SEC;

  // Reassign on Ping #5
  if ($rec['ping_count'] >= STOP_AFTER) {
    reassign_after_ping5($id, $assignedId);
    $rec['next_ping_ts'] = PHP_INT_MAX; // stop tracking; new owner/MOVED_TIME will reinit if still fresh
  }

  $store[$id] = $rec;
  $sent[] = ['lead'=>$id, 'agent'=>$agentTo, 'leaders'=>WA_TEAM_LEADS_GROUP, 'ping'=>$pingNo];
}

/* Drop store records that are no longer fresh */
if (!empty($store)) {
  $freshSet = array_flip($currentFreshIds);
  foreach ($store as $id => $rec) if (!isset($freshSet[$id])) unset($store[$id]);
}

save_store($store);

header('Content-Type: application/json; charset=utf-8');
echo json_encode([
  'checked' => count($leads),
  'sent'    => $sent,
  'time'    => date('c'),
  'agents_cache_used' => is_file(AGENTS_CACHE_FILE) && (time()-filemtime(AGENTS_CACHE_FILE) <= AGENTS_CACHE_TTL)
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
