<?php

// --- Timezone from .env ---
$TZ = $_ENV['TIMEZONE'] ?? 'Europe/Moscow';
try { $tz = new DateTimeZone($TZ); } catch(Throwable $e) { $tz = new DateTimeZone('Europe/Moscow'); }
@date_default_timezone_set($tz->getName());

// /var/www/leadbot/public/index.php
// Telegram webhook: старт / смена / привязка / взятие лида.
// Уважение флага «не получать лиды». Поддержка коэффициента выдачи (coef) на менеджера.
// Поддержка «псевдо-свободных» UON-менеджеров (например, 2).

header('Content-Type: application/json; charset=utf-8');

$ROOT = realpath(__DIR__ . '/..');
@mkdir("$ROOT/var", 0775, true);

$logSend = "$ROOT/var/send.log";
$logTake = "$ROOT/var/take.log";
$logUon  = "$ROOT/var/uon.log";
$logBind = "$ROOT/var/bind.log";
$logCb   = "$ROOT/var/callback.log"; // лог callback'ов

// ========== utils ==========
function logx($file, $msg){ @file_put_contents($file, date('c')." ".$msg.PHP_EOL, FILE_APPEND); }
function env_load($path){
  if (!is_readable($path)) return;
  foreach(@file($path, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) ?: [] as $line){
    if ($line===''||$line[0]==='#'||strpos($line,'=')===false) continue;
    [$k,$v]=explode('=',$line,2);
    $_ENV[trim($k)]=trim($v); putenv(trim($k).'='.trim($v));
  }
}
env_load(__DIR__.'/../config/.env');
$CONFIG_DIR = getenv('LEADBOT_CONFIG_DIR') ?: '/var/www/leadbot/config';
env_load($CONFIG_DIR.'/.env');

$UON_WEB = $_ENV['UON_BASE_WEB'] ?? 'https://pegaskazan.u-on.ru';

// ========== Telegram helpers ==========
function tg_post($method, $fields){
  $token = $_ENV['TELEGRAM_BOT_TOKEN'] ?? '';
  if ($token==='') return ['ok'=>false,'description'=>'no token'];
  $url = "https://api.telegram.org/bot{$token}/{$method}";
  $ch  = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_POST            => 1,
  CURLOPT_RETURNTRANSFER  => 1,
  CURLOPT_TIMEOUT         => 15,
  CURLOPT_CONNECTTIMEOUT  => 5,
  CURLOPT_IPRESOLVE       => CURL_IPRESOLVE_V4,
  CURLOPT_PROXY           => '127.0.0.1:1080',
  CURLOPT_PROXYTYPE       => CURLPROXY_SOCKS5,
  CURLOPT_POSTFIELDS      => $fields,
]);
  $out=curl_exec($ch); $err=curl_error($ch);
  curl_close($ch);
  if($out===false) return ['ok'=>false,'description'=>"curl: $err"];
  $j=json_decode($out,true);
  return is_array($j)? $j : ['ok'=>false,'description'=>"tg bad json: $out"];
}
function tg_send($chat_id,$text,$kb=null){
  global $logSend;
  $payload=['chat_id'=>$chat_id,'text'=>$text];
  if($kb) $payload['reply_markup']=json_encode($kb,JSON_UNESCAPED_UNICODE);
  $resp=tg_post('sendMessage',$payload);
  $preview = function_exists('mb_substr') ? mb_substr($text,0,140) : substr($text,0,140);
  logx($logSend,"SEND {$chat_id}: ".preg_replace('~\s+~',' ',$preview)." | resp=".json_encode($resp,JSON_UNESCAPED_UNICODE));
  return $resp;
}
function tg_edit_kb($chat_id,$message_id,$kbArr = null){
  $reply_markup = $kbArr ? json_encode($kbArr, JSON_UNESCAPED_UNICODE) : '{}';
  return tg_post('editMessageReplyMarkup',[
    'chat_id'=>$chat_id,'message_id'=>$message_id,'reply_markup'=>$reply_markup
  ]);
}
function tg_delete($chat_id,$message_id){
  return tg_post('deleteMessage',['chat_id'=>$chat_id,'message_id'=>$message_id]);
}

// ========== storage ==========
function leads_file(){ return __DIR__.'/../var/leads.json'; }
function leads_load(){ $f=leads_file(); if(!is_file($f)) return []; $j=json_decode(file_get_contents($f),true); return is_array($j)?$j:[]; }
function leads_save($d){
  $f=leads_file();
  $fp=@fopen($f,'c+');
  if($fp){
    flock($fp,LOCK_EX);
    ftruncate($fp,0);
    fwrite($fp,json_encode($d,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));
    fflush($fp);
    flock($fp,LOCK_UN);
    fclose($fp);
  } else {
    @file_put_contents($f,json_encode($d,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));
  }
}
function binds_file(){ return __DIR__.'/../var/binds.json'; }
function store_bind_load(){ $f=binds_file(); if(!is_file($f)) return []; $j=json_decode(file_get_contents($f),true); return is_array($j)?$j:[]; }
function bind_get($tg_id){
  $b=store_bind_load();
  $k=(string)$tg_id;
  return $b[$k] ?? ($b[$tg_id] ?? null); // подстраховка по типу ключа
}
function bind_is_locked($tg_id){
  $b=bind_get($tg_id); if(!$b) return false;
  return !empty($b['locked']) && empty($b['disabled']);
}
function binds_put($tg_id,$uon_id,$office_id,$source='rebind',$lock=true){
  global $logBind;
  $f=binds_file();
  $b=is_file($f)?(json_decode(@file_get_contents($f),true)?:[]):[];
  $prev = $b[(string)$tg_id] ?? [];
  $coef = isset($prev['coef']) ? (int)$prev['coef'] : 5;

  $b[(string)$tg_id]=[
    'uon_id'=>(int)$uon_id,
    'office_id'=>(int)$office_id,
    'uon_user'=>(int)$uon_id,
    'office'  =>(int)$office_id,
    'locked'  => (bool)$lock,
    'disabled'=> !empty($prev['disabled']),
    'suspend_leads'=> !empty($prev['suspend_leads']),
    'no_feed'      => !empty($prev['no_feed']),
    'coef'         => $coef,
    'bound_at'=> date('c'),
    'source'  => $source,
  ];

  // --- атомарная запись + верификация ---
  $tmp = $f.'.tmp';
  $json = json_encode($b, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
  $ok = ($json!==false) && (@file_put_contents($tmp,$json,LOCK_EX)!==false) && @rename($tmp,$f);
  if(!$ok){
    @unlink($tmp);
    logx($logBind,"ERROR: failed to write binds.json file=$f");
    return false;
  }
  $chk = json_decode(@file_get_contents($f), true);
  $exists = is_array($chk) && isset($chk[(string)$tg_id]) && (int)($chk[(string)$tg_id]['uon_id']??0)===(int)$uon_id;

  logx($logBind,"BOUND tg={$tg_id} uon={$uon_id} office={$office_id} src={$source} locked=".($lock?1:0)." coef={$coef} write_ok=1 verify=".($exists?1:0));
  return $exists;
}
// снять все привязки по UON ID менеджера
function binds_unbind_uon(int $uon_id): array {
  global $logBind;
  $f = binds_file();

  $b = is_file($f) ? (json_decode(@file_get_contents($f), true) ?: []) : [];
  if (!is_array($b)) $b = [];

  $before  = count($b);
  $uon_id  = (int)$uon_id;

  foreach ($b as $tgId => $rec) {
    $uid = (int)($rec['uon_id'] ?? $rec['uon_user'] ?? 0);
    if ($uid === $uon_id) {
      unset($b[$tgId]);
    }
  }

  $after   = count($b);
  $removed = $before - $after;

  // атомарная запись
  $tmp  = $f . '.tmp';
  $json = json_encode($b, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
  $ok   = ($json !== false)
       && (@file_put_contents($tmp, $json, LOCK_EX) !== false)
       && @rename($tmp, $f);

  if (!$ok) {
    @unlink($tmp);
    logx($logBind, "ERROR: failed to write binds.json in unbind uon={$uon_id}");
    return [false, 0];
  }

  logx($logBind, "UNBIND uon={$uon_id} removed={$removed}");
  return [true, $removed];
}

// ===== Статистика выдач (из leads.json) =====
function shift_start_ts_index(): int {
  $TZ = $_ENV['TIMEZONE'] ?? 'Europe/Moscow';
  try { $tz = new DateTimeZone($TZ);} catch(Throwable $e){ $tz = new DateTimeZone('Europe/Moscow'); }
  $now = new DateTimeImmutable('now', $tz);
  $h = (int)$now->format('G');
  $base = ($h < 9) ? (new DateTimeImmutable('yesterday 09:00', $tz)) : (new DateTimeImmutable('today 09:00', $tz));
  return $base->getTimestamp();
}
function stats_calc(array $leads, ?DateTimeZone $tz=null): array {
  if(!$tz){
    $TZ = $_ENV['TIMEZONE'] ?? 'Europe/Moscow';
    try { $tz = new DateTimeZone($TZ);} catch(Throwable $e){ $tz = new DateTimeZone('Europe/Moscow'); }
  }
  $today = (new DateTimeImmutable('today', $tz))->format('Y-m-d');
  $shiftStart = shift_start_ts_index();

  $total_today=0; $take_today=0; $auto_today=0;
  $total_shift=0; $take_shift=0; $auto_shift=0;

  foreach($leads as $id=>$info){
    $by = (string)($info['assigned_by'] ?? '');
    if ($by !== 'take' && $by !== 'auto') continue;
    $tsRaw = (string)($info['assigned_ts'] ?? '');
    if ($tsRaw === '') continue;
    $ts = strtotime($tsRaw); if ($ts===false) continue;

    if (substr($tsRaw,0,10) === $today){
      $total_today++; if($by==='take') $take_today++; else $auto_today++;
    }
    if ($ts >= $shiftStart){
      $total_shift++; if($by==='take') $take_shift++; else $auto_shift++;
    }
  }
  return compact('total_today','take_today','auto_today','total_shift','take_shift','auto_shift');
}
function stats_send_now(int $chat_id): void {
  $leads = leads_load();
  $tz = new DateTimeZone($_ENV['TIMEZONE'] ?? 'Europe/Moscow');
  $s = stats_calc($leads, $tz);
  $msg =
    " Статистика выдач бота\n".
    "Сегодня: {$s['total_today']} (авто {$s['auto_today']}, /take {$s['take_today']})\n".
    "За смену: {$s['total_shift']} (авто {$s['auto_shift']}, /take {$s['take_shift']})";
  tg_send($chat_id, $msg);
}

// найти TG по UON ID (для пометки external)
function bind_find_tg_by_uon($uonId){
  $b = store_bind_load();
  foreach($b as $tg=>$rec){
    $uid = (int)($rec['uon_id']??$rec['uon_user']??0);
    if($uid === (int)$uonId) return (int)$tg;
  }
  return null;
}

// ====== coef → дневной лимит ======
function coef_daily_limit(int $coef): int {
  $coef = ($coef>=1 && $coef<=5)?$coef:5;
  if ($coef >= 1 && $coef <= 4) return $coef;
  // coef=5 → применяем LEAD_RATE_MAX_PER_DAY (если 0/пусто — без лимита)
  $env = (int)($_ENV['LEAD_RATE_MAX_PER_DAY'] ?? 0);
  return $env > 0 ? $env : 0; // 0 == без дневного лимита
}
function effective_daily_limit_for_tg(int $tgId): int {
  $b = bind_get($tgId) ?: [];
  $coef = isset($b['coef']) ? (int)$b['coef'] : 5;
  return coef_daily_limit($coef);
}
// сколько сегодня уже выдано ботом (take/auto) этому TG
function today_taken_by_tg(array $leads, int $tgId, ?DateTimeZone $tz=null): int {
  if(!$tz){ $tz = new DateTimeZone($_ENV['TIMEZONE'] ?? 'Europe/Moscow'); }
  $today = (new DateTimeImmutable('today', $tz))->format('Y-m-d');
  $cnt = 0;
  foreach ($leads as $lid=>$info){
    if ((int)($info['assigned_to'] ?? 0) !== $tgId) continue;
    $by = (string)($info['assigned_by'] ?? '');
    if ($by !== 'take' && $by !== 'auto') continue; // считаем только работу бота
    $ts = (string)($info['assigned_ts'] ?? '');
    if ($ts !== '' && substr($ts,0,10) === $today) $cnt++;
  }
  return $cnt;
}

// удалить уведомления по лиду кроме одного чата (если задан)
function purge_lead_messages_except(int $leadId, ?int $keep_chat_id = null): void {
  $f = leads_file();
  if (!is_readable($f)) return;
  $j = json_decode(file_get_contents($f), true) ?: [];
  $msgs = $j[$leadId]['msgs'] ?? [];
  if (!$msgs) return;

  $kept = [];
  foreach ($msgs as $m) {
    $cid = (int)$m['chat_id'];
    $mid = (int)$m['message_id'];
    if ($keep_chat_id !== null && $cid === $keep_chat_id) {
      $kept[] = $m;
      continue;
    }
    tg_delete($cid,$mid);
    usleep(30000);
  }
  $j[$leadId]['msgs'] = $kept;
  file_put_contents($f, json_encode($j, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));
}

// заменить клавиатуру у взявшего на ссылку в U-ON
function set_link_kb_for_taker(int $leadId, int $taker_chat_id, string $href): void {
  $f = leads_file();
  if (!is_readable($f)) return;
  $j = json_decode(file_get_contents($f), true) ?: [];
  $msgs = $j[$leadId]['msgs'] ?? [];
  if (!$msgs) return;

  $kb = ['inline_keyboard' => [[['text'=>'Перейти к обращению в U-ON', 'url'=>$href]]]];
  foreach ($msgs as $m) {
    if ((int)$m['chat_id'] !== $taker_chat_id) continue;
    tg_edit_kb($m['chat_id'],$m['message_id'],$kb);
    usleep(30000);
  }
  // после замены — чистим «хвост», чтобы reminder их больше не трогал
  $j[$leadId]['msgs'] = [];
  file_put_contents($f, json_encode($j, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));
}

// ========== rate limiting ==========
function rate_file(){ return __DIR__.'/../var/rate.json'; }
function rate_load(){ $f=rate_file(); if(!is_file($f)) return []; $j=json_decode(file_get_contents($f),true); return is_array($j)?$j:[]; }
function rate_save($data){ @file_put_contents(rate_file(), json_encode($data, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT)); }
function rate_can_take($tg_id){
  $cool1 = (int)($_ENV['LEAD_RATE_COOLDOWN_AFTER_1'] ?? 900);
  $cool2 = (int)($_ENV['LEAD_RATE_COOLDOWN_AFTER_2'] ?? 1800);
  $maxHr = (int)($_ENV['LEAD_RATE_MAX_PER_HOUR'] ?? 2);

  $now = time(); $since1h = $now - 3600;
  $db = rate_load();
  $row = $db[(string)$tg_id]['events'] ?? [];
  $row = array_values(array_filter($row, fn($t)=> ($now - (int)$t) <= 7200));

  $lastHour = array_values(array_filter($row, fn($t)=> (int)$t >= $since1h));
  $cnt = count($lastHour);

  if ($maxHr > 0 && $cnt >= $maxHr){
    sort($lastHour);
    $earliest = $lastHour[0];
    $wait = max(0, ($earliest + 3600) - $now);
    return [false, $wait, 'hour_limit'];
  }
  if ($cnt === 1) {
    $last = max($lastHour);
    $needReadyAt = $last + max(0,$cool1);
    if ($now < $needReadyAt) return [false, $needReadyAt - $now, 'cooldown1'];
  } elseif ($cnt >= 2) {
    $last = max($lastHour);
    $needReadyAt = $last + max(0,$cool2);
    if ($now < $needReadyAt) return [false, $needReadyAt - $now, 'cooldown2'];
  }
  return [true, null, null];
}
function rate_track_success($tg_id){
  $db = rate_load();
  $arr = $db[(string)$tg_id]['events'] ?? [];
  $arr[] = time();
  $now = time();
  $arr = array_values(array_filter($arr, fn($t)=> ($now - (int)$t) <= 7200));
  $db[(string)$tg_id] = ['events'=>$arr];
  rate_save($db);
}

// ========== config readers ==========
function offices_load(){
  global $CONFIG_DIR;
  $f=$CONFIG_DIR.'/offices.json'; if(!is_readable($f)) return [];
  $j=json_decode(file_get_contents($f),true) ?: [];
  $list=$j['offices']??[]; $out=[];
  foreach($list as $o){
    $id=(int)($o['uon_office_id']??0); if($id<=0) continue;
    $out[$id]=['id'=>$id,'name'=>(string)($o['name']??("Офис #$id")),'active'=>!empty($o['active'])];
  }
  return $out;
}
function managers_load(){
  global $CONFIG_DIR;
  $f=$CONFIG_DIR.'/managers.json'; if(!is_readable($f)) return [];
  $j=json_decode(file_get_contents($f),true) ?: [];
  $arr=$j['managers']??($j['users']??($j['items']??[])); $out=[];
  foreach($arr as $m){
    $uid=(int)($m['uon_user_id']??$m['id']??0); if($uid<=0) continue;
    $name=trim((string)($m['full_name']??(($m['surname']??'').' '.($m['name']??'').' '.($m['sname']??''))));
    $off=(int)($m['office_uon_id']??$m['office_id']??0);
    $active=isset($m['active'])?(bool)$m['active']:true;
    $out[]=['uon_id'=>$uid,'full_name'=>($name!==''?$name:"UON #$uid"),'office_id'=>$off,'active'=>$active,'tg_id'=>isset($m['tg_id'])?(int)$m['tg_id']:null,'tg_username'=>$m['tg_username']??null];
  }
  return $out;
}
const ONLY_OFFICES_WITH_ACTIVE_MANAGERS = false;

// ========== keyboards / shift UI ==========
function kb_offices(){
  $offices=offices_load(); $managers=managers_load(); $has=[];
  foreach($managers as $m){ if($m['active'] && $m['office_id']>0) $has[$m['office_id']]=true; }
  ksort($offices); $rows=[];
  foreach($offices as $id=>$o){
    if(!$o['active']) continue;
    if(ONLY_OFFICES_WITH_ACTIVE_MANAGERS && empty($has[$id])) continue;
    $rows[]=[[ 'text'=>$o['name'], 'callback_data'=>"off:$id" ]];
  }
  if(!$rows) $rows=[[[ 'text'=>'Нет доступных офисов','callback_data'=>'noop' ]]];
  return ['inline_keyboard'=>$rows];
}
function kb_managers($office_id){
  $rows=[];
  foreach(managers_load() as $m){
    if(!$m['active']) continue;
    if((int)$m['office_id']!==(int)$office_id) continue;
    $rows[]=[[ 'text'=>$m['full_name'], 'callback_data'=>"man:{$m['uon_id']}:{$office_id}" ]];
  }
  if(!$rows) $rows=[[[ 'text'=>'Нет менеджеров','callback_data'=>'noop' ]]];
  return ['inline_keyboard'=>$rows];
}
function shift_kb_for($tg_id){
  $f=__DIR__.'/../var/state.json';
  $s=file_exists($f)? json_decode(file_get_contents($f),true):[];
  $curRec = $s[(string)$tg_id] ?? [];
  $cur = ($curRec['shift'] ?? 'close') === 'open' ? 'open' : 'close';
  if ($cur === 'open') {
    $text = "Смена: ОТКРЫТА";
    $btn  = ['text'=>'Закрыть смену','callback_data'=>'shift:close'];
  } else {
    $text = "Смена: ЗАКРЫТА";
    $btn  = ['text'=>'Открыть смену','callback_data'=>'shift:open'];
  }
  $kb = ['inline_keyboard' => [[ $btn ]]];
  return [$text,$kb];
}

// ========== ADMIN & TOKENS ==========
function is_admin($tg_id){
  $raw=getenv('TELEGRAM_ADMIN_IDS') ?: ($_ENV['TELEGRAM_ADMIN_IDS']??'');
  $ids=array_filter(array_map('trim',explode(',',$raw)));
  foreach($ids as $s){ if((string)$tg_id===$s) return true; } return false;
}
function tokens_file(){ return __DIR__.'/../var/bind_tokens.json'; }
function tokens_load(){ $f=tokens_file(); if(!is_file($f)) return []; $j=json_decode(file_get_contents($f),true); return is_array($j)?$j:[]; }
function tokens_save($t){ file_put_contents(tokens_file(),json_encode($t,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT)); }
function token_make($len=24){ $raw=bin2hex(random_bytes($len)); return substr($raw,0,48); }

// ========== UON helpers ==========
function uon_get($path){
  global $logUon;
  $key=$_ENV['UON_API_KEY']??''; $base=$_ENV['UON_API_BASE']??'https://api.u-on.ru';
  if($key==='') return [0,null,'no key'];
  $url=rtrim($base,'/')."/{$key}/".$path;
  $ch=curl_init($url);
  curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>1,CURLOPT_TIMEOUT=>12]);
  $out=curl_exec($ch); $code=curl_getinfo($ch,CURLINFO_RESPONSE_CODE); curl_close($ch);
  logx($logUon,"GET url={$url} code={$code} resp=".$out);
  $j=json_decode($out,true); return [$code,$j,$out];
}
function uon_post($path,$fields){
  global $logUon;
  $key=$_ENV['UON_API_KEY']??''; $base=$_ENV['UON_API_BASE']??'https://api.u-on.ru';
  if($key==='') return [0,null,'no key'];
  $url=rtrim($base,'/')."/{$key}/".$path;
  $ch=curl_init($url);
  curl_setopt_array($ch,[
    CURLOPT_POST=>1, CURLOPT_RETURNTRANSFER=>1, CURLOPT_TIMEOUT=>15,
    CURLOPT_HTTPHEADER=>['Content-Type: application/x-www-form-urlencoded; charset=UTF-8'],
    CURLOPT_POSTFIELDS=>http_build_query($fields,'','&',PHP_QUERY_RFC3986),
  ]);
  $out=curl_exec($ch); $code=curl_getinfo($ch,CURLINFO_RESPONSE_CODE); curl_close($ch);
  logx($logUon,"UPDATE url={$url} code={$code} payload=".json_encode($fields,JSON_UNESCAPED_UNICODE)." resp=".$out);
  $j=json_decode($out,true); return [$code,$j,$out];
}
function uon_assign($leadId,$managerUonId,$officeId){
  return uon_post("request/update/{$leadId}.json",[ 'manager_id'=>$managerUonId, 'office_id'=>$officeId ]);
}

/* ===== Универсальный фетчер записи (lead → request) ===== */
function uon_fetch_record(int $leadId): array {
  [$c1, $j1] = uon_get("lead/{$leadId}.json");
  if ($c1 === 200 && is_array($j1)) {
    $rec1 = $j1['lead'][0] ?? $j1['lead'] ?? null;
    if (is_array($rec1) && $rec1) return [true, 'lead', 200, $rec1];
  }
  [$c2, $j2] = uon_get("request/{$leadId}.json");
  if ($c2 === 200 && is_array($j2)) {
    $rec2 = $j2['request'] ?? ($j2['lead'][0] ?? $j2['lead'] ?? null);
    if (is_array($rec2) && $rec2) return [true, 'request', 200, $rec2];
  }
  if ($c1 === 404 && $c2 === 404) return [false, 'request', 404, null];
  $code = ($c1 !== 200 && $c1 !== 404) ? $c1 : (($c2 !== 200 && $c2 !== 404) ? $c2 : 404);
  return [false, 'request', $code, null];
}

/* ===== Lead status policy (ВАЖНО) ===== */

// Разрешённые статусы лида (из .env или по справочнику status_lead)
function allowed_lead_status_ids(): array {
  $raw = trim(getenv('BOT_ALLOWED_LEAD_STATUS_IDS') ?: ($_ENV['BOT_ALLOWED_LEAD_STATUS_IDS'] ?? ''));
  if ($raw !== '') {
    return array_values(array_filter(array_map('intval', explode(',', $raw)), fn($x)=>$x>0));
  }
  // фолбэк: попробуем найти «Новый» в /status_lead.json
  [$code, $j] = uon_get('status_lead.json');
  if ($code === 200 && isset($j['status_lead']) && is_array($j['status_lead'])) {
    foreach ($j['status_lead'] as $s) {
      $name = mb_strtolower((string)($s['name'] ?? ''), 'UTF-8');
      if (in_array($name, ['новый','new'], true)) {
        return [(int)$s['id']];
      }
    }
  }
  // самый жёсткий фолбэк
  return [1];
}

// Унифицированное извлечение ID статуса из разных ответов U-ON
function uon_extract_status_id(array $rec): ?int {
  foreach (['lead_status_id','status_lead_id','status_id','request_status_id'] as $k) {
    if (array_key_exists($k, $rec) && (string)$rec[$k] !== '') {
      return (int)$rec[$k];
    }
  }
  return null; // не удалось определить
}

/** Вернёт: [ok, httpCode, kind("lead"|"request"), managerId|null, leadStatusId|null] */
function uon_mgr_and_status(int $leadId): array {
  // 1) /lead/{id}.json
  [$c1, $j1] = uon_get("lead/{$leadId}.json");
  if ($c1 === 200 && is_array($j1)) {
    $rec = $j1['lead'][0] ?? $j1['lead'] ?? null;
    if (is_array($rec)) {
      $mgr = (int)($rec['manager_id'] ?? 0);
      $ls  = uon_extract_status_id($rec);
      return [true, 200, 'lead', $mgr, $ls];
    }
  } elseif ($c1 === 404) {
    return [false, 404, 'lead', null, null];
  }

  // 2) /request/{id}.json (fallback)
  [$c2, $j2] = uon_get("request/{$leadId}.json");
  if ($c2 === 200 && is_array($j2)) {
    $rec = $j2['request'] ?? ($j2['lead'][0] ?? $j2['lead'] ?? null);
    if (is_array($rec)) {
      $mgr = (int)($rec['manager_id'] ?? 0);
      $ls  = uon_extract_status_id($rec);
      return [true, 200, 'request', $mgr, $ls];
    }
  }

  return [false, $c2 ?: $c1, 'request', null, null];
}

// ===== Псевдо-свободные UON-менеджеры =====
function pseudo_free_mgr_set(): array {
  // список менеджеров, которых считаем как "свободно" (по умолчанию 2). 0 всегда свободен.
  $raw = getenv('BOT_PSEUDO_FREE_UON_MGR_IDS') ?: ($_ENV['BOT_PSEUDO_FREE_UON_MGR_IDS'] ?? '2');
  $ids = array_filter(array_map('trim', explode(',', (string)$raw)), fn($x)=>$x!=='');
  $set = [];
  foreach ($ids as $v) { $set[(int)$v]=true; }
  $set[0]=true;
  return $set;
}
function is_pseudo_free_mgr(?int $mgr): bool {
  static $set=null;
  if ($set===null) $set=pseudo_free_mgr_set();
  return isset($set[(int)$mgr]);
}

// Подтверждение назначения — через универсальный фетчер
function uon_get_lead_manager($leadId){
  [$ok, $kind, $code, $rec] = uon_fetch_record((int)$leadId);
  if (!$ok || !is_array($rec)) return [false, $code, null];
  $mgr = (int)($rec['manager_id'] ?? 0);
  return [true, 200, $mgr];
}

// ========== Reminder cancel (жёстко гасим любые напоминания по лиду) ==========
function remind_cancel($leadId){
  $f = __DIR__.'/../var/reminders.json';
  $j = is_file($f) ? (json_decode(file_get_contents($f), true) ?: []) : [];
  if (!isset($j[$leadId])) $j[$leadId] = [];
  $j[$leadId]['done'] = true;
  file_put_contents($f, json_encode($j, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));
}

// ========== TAKE flow ==========
function do_take($leadId,$from_id,$chat_id){
  global $logTake, $UON_WEB;
  logx($logTake,"---- TAKE start lead={$leadId} from={$from_id} chat={$chat_id}");

  $me=bind_get($from_id);
  if(!$me){ tg_send($chat_id,"Сначала выполните /rebind и выберите себя."); logx($logTake,"no-bind from={$from_id}"); return; }

  // Админская пауза выдачи лидов
  if (!empty($me['suspend_leads']) || !empty($me['no_feed'])) {
    tg_send($chat_id, "⚠️ Выдача лидов для вас временно приостановлена администратором.");
    logx($logTake, "suspended from={$from_id}");
    return;
  }

  $myUon=(int)($me['uon_user']??$me['uon_id']??0);
  $myOff=(int)($me['office_id']??$me['office']??($_ENV['PILOT_OFFICE_ID']??3));
  logx($logTake,"bind=".json_encode(['uon'=>$myUon,'office'=>$myOff],JSON_UNESCAPED_UNICODE));

  // --- ДНЕВНОЙ ЛИМИТ ПО COEF ---
  $leadsAll = leads_load();
  $tz = new DateTimeZone($_ENV['TIMEZONE'] ?? 'Europe/Moscow');
  $effectiveDayLimit = effective_daily_limit_for_tg((int)$from_id); // 0 = без лимита
  if ($effectiveDayLimit > 0) {
    $takenToday = today_taken_by_tg($leadsAll, (int)$from_id, $tz);
    if ($takenToday >= $effectiveDayLimit) {
      tg_send($chat_id, "⏳ Лимит на сегодня исчерпан: {$takenToday} из {$effectiveDayLimit}.");
      logx($logTake, "limit-day hit tg={$from_id} today={$takenToday} max={$effectiveDayLimit}");
      return;
    }
  }

  // Антиспам (по часу/кулдауны — старая логика, остаётся)
  [$okRate, $waitSec, $why] = rate_can_take($from_id);
  if(!$okRate){
    $min = ceil($waitSec/60);
    $msg = $waitSec < 60
      ? "⏳ Пауза между лидами. Подождите ~{$waitSec} сек."
      : "⏳ Пауза между лидами. Подождите примерно {$min} мин.";
    tg_send($chat_id, $msg);
    logx($logTake, "rate-limit from={$from_id} wait={$waitSec}s reason={$why}");
    return;
  }

  // Проверка статуса лида в U-ON (строго по lead_status_id || request_status_id)
  [$ok,$code,$kind,$remoteMgr,$leadStatus] = uon_mgr_and_status($leadId);
  if(!$ok){
    tg_send($chat_id,"⏳ Статус лида в U-ON временно недоступен (HTTP {$code}). Попробуйте ещё раз.");
    logx($logTake,"read-fail code={$code}"); return;
  }
  $allowed = allowed_lead_status_ids();
  if ($leadStatus === null || !in_array((int)$leadStatus, $allowed, true)) {
    tg_send($chat_id, "Нельзя взять: статус (lead_status_id={$leadStatus}) не «Новый»."); 
    purge_lead_messages_except($leadId, null);
    logx($logTake,"status-deny lead={$leadId} status={$leadStatus} allowed=".json_encode($allowed));
    return;
  }

  // Если в UON уже есть менеджер и он НЕ псевдо-свободный — прекращаем
  if((int)$remoteMgr>0 && !is_pseudo_free_mgr((int)$remoteMgr)){
    if($remoteMgr===$myUon) tg_send($chat_id,"Лид #{$leadId} уже ваш ✅");
    else tg_send($chat_id,"⚠️ Лид #{$leadId} уже назначен другому менеджеру (UON ID {$remoteMgr}).");
    purge_lead_messages_except($leadId, null);
    logx($logTake,"already-assigned hard mgr={$remoteMgr}");
    return;
  }
  // Если менеджер псевдо-свободный (например, 2) — продолжаем брать как свободный
  if ((int)$remoteMgr>0 && is_pseudo_free_mgr((int)$remoteMgr)) {
    logx($logTake,"pseudo-free mgr detected ({$remoteMgr}) — proceed to assign");
  }

  // Назначение
  [$c2,$j2,$raw2]=uon_assign($leadId,$myUon,$myOff);
  $ok2=($c2==200)&&is_array($j2)&&(int)($j2['result']??0)===200;
  logx($logTake,"assign result ok=".($ok2?1:0)." code={$c2}");
  if(!$ok2){
    tg_send($chat_id,"⛔️ Не удалось назначить (код {$c2}). Возможно, лид уже занят.");
    purge_lead_messages_except($leadId,null);
    return;
  }

  // Повторная проверка
  [$ok3,$code3,$mgr3]=uon_get_lead_manager($leadId);
  if(!$ok3){
    tg_send($chat_id,"Назначили, но не удалось подтвердить в UON (HTTP {$code3}). Проверьте вручную.");
    purge_lead_messages_except($leadId,null);
    return;
  }
  if((int)$mgr3!==$myUon){
    tg_send($chat_id,"⚠️ Похоже, кто-то опередил — лид уже назначен UON ID {$mgr3}.");
    purge_lead_messages_except($leadId,null);
    return;
  }

  // Успех > фиксируем локально
  $leads=leads_load();
  if(!isset($leads[$leadId])) $leads[$leadId]=[];
  $leads[$leadId]['assigned_to']=$from_id;
  $leads[$leadId]['assigned_ts']=date('c');
  $leads[$leadId]['assigned_by']='take';
  leads_save($leads);

  // Ссылка
  $href = rtrim($UON_WEB,'/')."/request_edit.php?r_id=".$leadId;

  // Удаляем уведомления всем, КРОМЕ взявшего
  purge_lead_messages_except($leadId, $from_id);

  // В сообщении взявшего — ставим кнопку-ссылку
  set_link_kb_for_taker($leadId, $from_id, $href);

  // Личное подтверждение
  tg_post('sendMessage', [
    'chat_id' => $chat_id,
    'text'    => "✅ Лид #{$leadId} назначен вам",
    'reply_markup' => json_encode(['inline_keyboard'=>[[['text'=>'Перейти к обращению в U-ОН','url'=>$href]]]], JSON_UNESCAPED_UNICODE)
  ]);

  // Отменяем будущие напоминания и фиксируем rate
  remind_cancel($leadId);
  rate_track_success($from_id);
}

// ========== webhook payload ==========
$raw=file_get_contents('php://input') ?: '{}';
$u=json_decode($raw,true);
if(!is_array($u)) $u=[];

// ========== messages ==========
if(isset($u['message'])){
  $m=$u['message']; $chat_id=$m['chat']['id'] ?? null; $from_id=$m['from']['id'] ?? null;
  $text=trim($m['text']??'');
  if(!$chat_id || !$from_id){ echo '{"ok":true}'; exit; }

  // deep-link: /start bind_<token>
  if (preg_match('~^/start\s+bind_([A-Fa-f0-9]{16,})$~', $text, $mm)) {
    if (bind_is_locked($from_id) && !is_admin($from_id)) {
      tg_send($chat_id,"У вас уже есть привязка. Смену может выполнить только администратор.");
      echo '{"ok":true}'; exit;
    }
    $tok=$mm[1]; $toks=tokens_load();
    if(!isset($toks[$tok])){ tg_send($chat_id,"Недействительная ссылка."); echo '{"ok":true}'; exit; }
    $rec=$toks[$tok];
    if(!empty($rec['used'])){ tg_send($chat_id,"Ссылка уже использована."); echo '{"ok":true}'; exit; }
    if(($rec['expires']??0)<time()){ tg_send($chat_id,"Срок действия ссылки истёк."); echo '{"ok":true}'; exit; }

    $uon=(int)$rec['uon_id']; $off=(int)$rec['office_id'];
    binds_put($from_id,$uon,$off,'token',true);
    $toks[$tok]['used']=true; tokens_save($toks);

    tg_send($chat_id,"Привязка выполнена ✅\nUON ID: {$uon}\nОфис: {$off}\n\nТеперь можно: /open, затем /take <ID>.");
    echo '{"ok":true}'; exit;
  }

  // /start
  if ($text === '/start') {
    $existingBind = bind_get($from_id);
    if (!$existingBind) {
      tg_post('sendMessage', [
        'chat_id' => $chat_id,
        'text'    => "Шаг 1/2: выбери офис",
        'reply_markup' => json_encode(kb_offices(), JSON_UNESCAPED_UNICODE)
      ]);
      echo '{"ok":true}'; exit;
    }
    list($label, $kb) = shift_kb_for($from_id);
    tg_send($chat_id, "Бот жив ✅\n{$label}", $kb);
    tg_send($chat_id,
      "Команды:\n".
      "/open — открыть смену\n".
      "/close — закрыть смену\n".
      "/rebind — сменить привязку\n".
      "/take <ID> — взять лид\n".
      "/shift — показать статус смены\n".
      "/stats — статистика выдач"
    );
    // Показать текущую статистику выдач
    stats_send_now($chat_id);
    echo '{"ok":true}'; exit;
  }

  if($text==='/shift'){
    list($label,$kb) = shift_kb_for($from_id);
    tg_send($chat_id, $label, $kb);
    echo '{"ok":true}'; exit;
  }

  if ($text === '/stats') {
    stats_send_now($chat_id);
    echo '{"ok":true}'; exit;
  }

  // админские токены
  if (preg_match('~^/issuebind\s+(\d+)\s+(\d+)(?:\s+(\d+))?~',$text,$m2) && is_admin($from_id)) {
    $uon=(int)$m2[1]; $office=(int)$m2[2]; $days=isset($m2[3])?max(1,(int)$m2[3]):7;
    $toks=tokens_load(); $tok=token_make();
    $toks[$tok]=['uon_id'=>$uon,'office_id'=>$office,'created'=>date('c'),'expires'=>time()+86400*$days,'used'=>false];
    tokens_save($toks);
    $botUser=$_ENV['TELEGRAM_BOT_USERNAME']??'';
    $hint=$botUser!=='' ? "t.me/{$botUser}?start=bind_{$tok}" : "/start bind_{$tok}";
    tg_send($chat_id,"Выдал одноразовую ссылку (действует {$days} дн.):\nUON={$uon}, офис={$office}\n\n{$hint}");
    echo '{"ok":true}'; exit;
  }
  if($text==='/tokens' && is_admin($from_id)){
    $toks=tokens_load(); $now=time(); $lines=[];
    foreach($toks as $k=>$v){
      $state=!empty($v['used'])?'USED':((($v['expires']??0)<$now)?'EXPIRED':'ACTIVE');
      $lines[]=substr($k,0,12)."…  UON:".($v['uon_id']??0)." off:".($v['office_id']??0)."  {$state}  exp:".date('Y-m-d H:i',($v['expires']??0));
    }
    tg_send($chat_id, $lines?implode("\n",$lines):"Нет токенов."); echo '{"ok":true}'; exit;
  }
  if(preg_match('~^/revoke\s+([A-Fa-f0-9]{16,})~',$text,$m3) && is_admin($from_id)){
    $tok=$m3[1]; $toks=tokens_load();
    if(isset($toks[$tok])){ unset($toks[$tok]); tokens_save($toks); tg_send($chat_id,"Ок, отозвал."); }
    else tg_send($chat_id,"Такого токена нет.");
    echo '{"ok":true}'; exit;
  }
    // /unbind <UON_ID> — снять все привязки для менеджера по его UON ID (только админ)
  if (preg_match('~^/unbind\s+(\d+)~', $text, $mm) && is_admin($from_id)) {
    $targetUon = (int)$mm[1];
    if ($targetUon <= 0) {
      tg_send($chat_id, "Использование:\n/unbind <UON_ID>\n\nНапример:\n/unbind 62918");
      echo '{"ok":true}'; exit;
    }

    [$ok, $removed] = binds_unbind_uon($targetUon);
    if (!$ok) {
      tg_send($chat_id, "❌ Ошибка при записи binds.json. Привязки не изменены.");
    } else {
      if ($removed > 0) {
        tg_send($chat_id, "✅ Снял привязки для UON ID {$targetUon}.\nУдалено записей: {$removed}.");
      } else {
        tg_send($chat_id, "ℹ️ Для UON ID {$targetUon} привязок не найдено.");
      }
    }
    echo '{"ok":true}'; exit;
  }

  // смена (команды)
  if($text==='/open'){
    $f=__DIR__.'/../var/state.json';
    $s=is_file($f)? (json_decode(file_get_contents($f),true) ?: []) : [];
    $s[(string)$from_id]=['shift'=>'open','ts'=>date('c'),'busy_till'=>0];
    $tmp=$f.'.tmp';
    $okWrite = (@file_put_contents($tmp, json_encode($s,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT), LOCK_EX)!==false) && @rename($tmp,$f);
    if(!$okWrite){
      @unlink($tmp);
      tg_send($chat_id,'Ошибка записи state.json — проверь права на var/');
    } else {
      tg_send($chat_id,'Смена открыта ✅');
      // Сразу показать статистику за смену/сегодня
      stats_send_now($chat_id);
    }
    echo '{"ok":true}'; exit;
  }
  if($text==='/close'){
    $f=__DIR__.'/../var/state.json';
    $s=is_file($f)? (json_decode(file_get_contents($f),true) ?: []) : [];
    $s[(string)$from_id]=['shift'=>'close','ts'=>date('c'),'busy_till'=>0];
    $tmp=$f.'.tmp';
    $okWrite = (@file_put_contents($tmp, json_encode($s,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT), LOCK_EX)!==false) && @rename($tmp,$f);
    if(!$okWrite){
      @unlink($tmp);
      tg_send($chat_id,'Ошибка записи state.json — проверь права на var/');
    } else {
      tg_send($chat_id,'Смена закрыта ✅');
    }
    echo '{"ok":true}'; exit;
  }

  // /rebind
  if ($text === '/rebind') {
    if (bind_is_locked($from_id) && !is_admin($from_id)) {
      $b = bind_get($from_id);
      $msg = "У вас уже есть привязка:\nUON ID: ".($b['uon_id']??'')."\nОфис: ".($b['office_id']??'')."\n\nСмену может выполнить только администратор.";
      tg_send($chat_id, $msg);
      echo '{"ok":true}'; exit;
    }
    tg_post('sendMessage',[
      'chat_id'=>$chat_id,
      'text'=>"Шаг 1/2: выбери офис",
      'reply_markup'=>json_encode(kb_offices(),JSON_UNESCAPED_UNICODE)
    ]);
    echo '{"ok":true}'; exit;
  }

  // /take ID
  if(preg_match('~^/take\s+(\d+)~',$text,$mm)){
    do_take((int)$mm[1],$from_id,$chat_id); echo '{"ok":true}'; exit;
  }
}

// ========== callbacks ==========
if(isset($u['callback_query'])){
  $cb   = $u['callback_query'];
  $cid  = (int)($cb['message']['chat']['id'] ?? 0);
  $mid  = (int)($cb['message']['message_id'] ?? 0);
  $from = $cb['from'] ?? ['id'=>0];
  $fid  = (int)($from['id'] ?? 0);
  $data = (string)($cb['data'] ?? '');

  $ack = function($txt='') use ($cb){
    tg_post('answerCallbackQuery', [
      'callback_query_id' => $cb['id'],
      'text'              => $txt,
      'show_alert'        => false
    ]);
  };
  if(!$cid || !$fid){ echo '{"ok":true}'; exit; }

  logx($logCb, "CB raw data=".json_encode($cb,JSON_UNESCAPED_UNICODE));

  $locked = bind_is_locked($fid) && !is_admin($fid);

  if ($data === 'shift:open' || $data === 'shift:close') {
    logx($logCb, "CB shift data={$data} fid={$fid} cid={$cid} mid={$mid}");
    $want = ($data === 'shift:open') ? 'open' : 'close';
    $f = __DIR__.'/../var/state.json';

    $s = is_file($f) ? (json_decode(file_get_contents($f), true) ?: []) : [];
    if (!is_array($s)) $s = [];
    $s[(string)$fid] = ['shift'=>$want,'ts'=>date('c'),'busy_till'=>0];

    $tmp = $f.'.tmp';
    $json = json_encode($s, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
    $okWrite = ($json!==false) && (@file_put_contents($tmp, $json, LOCK_EX)!==false) && @rename($tmp, $f);

    if(!$okWrite){
      @unlink($tmp);
      logx($logCb, "CB shift WRITE_FAIL want={$want} file={$f}");
      tg_post('answerCallbackQuery', [
        'callback_query_id' => $cb['id'],
        'text'              => "Ошибка записи state.json — проверьте права на var/",
        'show_alert'        => true
      ]);
      echo '{"ok":true}'; exit;
    }

    tg_post('answerCallbackQuery', [
      'callback_query_id' => $cb['id'],
      'text'              => ($want==='open' ? 'Смена открыта ✅' : 'Смена закрыта ✅'),
      'show_alert'        => false
    ]);
    list($label,$kb2) = shift_kb_for($fid);
    tg_post('editMessageText', [
      'chat_id'=>$cid,'message_id'=>$mid,
      'text'=>$label,'reply_markup'=>json_encode($kb2, JSON_UNESCAPED_UNICODE)
    ]);

    logx($logCb, "CB shift OK want={$want} fid={$fid}");
    echo '{"ok":true}'; exit;
  }

  if (preg_match('~^take:(\d+)$~', $data, $mm)) {
    $ack('Обрабатываю…');
    do_take((int)$mm[1], $fid, $cid);
    echo '{"ok":true}'; exit;
  }

  if (preg_match('~^off:(\d+)$~', $data, $m)) {
    if ($locked) { $ack('Уже привязан. Смена — через администратора.'); echo '{"ok":true}'; exit; }
    $office=(int)$m[1];
    tg_post('editMessageText', [
      'chat_id'=>$cid,'message_id'=>$mid,
      'text'=>"Шаг 2/2: выбери себя из списка",
      'reply_markup'=>json_encode(kb_managers($office), JSON_UNESCAPED_UNICODE)
    ]);
    echo '{"ok":true}'; exit;
  }

  if ($data === 'noop') { $ack('Недоступно'); echo '{"ok":true}'; exit; }

  if (preg_match('~^man:(\d+):(\d+)$~', $data, $m)) {
    if ($locked && !is_admin($fid)) { $ack('Уже привязан. Смена — через администратора.'); echo '{"ok":true}'; exit; }
    $uon=(int)$m[1]; $office=(int)$m[2];
    binds_put($fid, $uon, $office, 'rebind', true);
    tg_post('editMessageText', [
      'chat_id'=>$cid,'message_id'=>$mid,
      'text'=>"Привязка выполнена ✅\nUON ID: $uon\nОфис: $office"
    ]);
    tg_send($cid,"Вы привязаны. Теперь можно:\n— /open — открыть смену\n— /close — закрыть смену\n— /take <ID> — взять лид");
    echo '{"ok":true}'; exit;
  }

  $ack();
  echo '{"ok":true}'; exit;
}

// Всегда 200
http_response_code(200);
echo '{"ok":true}';
