<?php
declare(strict_types=1);
header('X-Content-Type-Options: nosniff');

function json_out($ok,$result=null,$error=null,$code=200){
 http_response_code($code);
 header('Content-Type: application/json; charset=utf-8');
 echo json_encode(['ok'=>$ok]+($ok?['result'=>$result]:['error'=>$error]),JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
 exit;
}
function relay_log($level,$stage,$message,$context=[]){
 $row=['time'=>gmdate('c'),'level'=>$level,'stage'=>$stage,'message'=>$message,'context'=>$context];
 @error_log(json_encode($row,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES).PHP_EOL,3,__DIR__.'/relay-media.log');
}
function relay_log_tail($limit=120){
 $file=__DIR__.'/relay-media.log';
 if(!is_file($file))return [];
 $lines=@file($file,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
 if(!is_array($lines))return [];
 $rows=[];
 foreach(array_reverse(array_slice($lines,-max(1,min(300,(int)$limit)))) as $line){
  $j=json_decode($line,true); if(is_array($j))$rows[]=$j;
 }
 return $rows;
}
function auth_config(){
 $cf=__DIR__.'/config.php';
 if(!is_file($cf))json_out(false,null,'Relay not configured',503);
 $c=require $cf;
 if(!is_array($c))json_out(false,null,'Invalid config',500);
 return $c;
}
function auth_headers($c,$raw,$mode='POST',$meta=''){
 $site=(string)($_SERVER['HTTP_X_MESORA_SITE']??'');
 $ts=(string)($_SERVER['HTTP_X_MESORA_TIMESTAMP']??'');
 $sig=(string)($_SERVER['HTTP_X_MESORA_SIGNATURE']??'');
 if($site===''||!hash_equals((string)($c['site_id']??''),$site)||!ctype_digit($ts)||abs(time()-(int)$ts)>180||$sig===''){
  json_out(false,null,'Unauthorized',401);
 }
 if($mode==='PUT'){
  $body_hash=hash('sha256',$raw);
  $signed=$ts.'.PUT.'.$meta.'.'.$body_hash;
 } else {
  $signed=$ts.'.'.$raw;
 }
 $want=hash_hmac('sha256',$signed,(string)$c['shared_secret']);
 if(!hash_equals($want,$sig))json_out(false,null,'Bad signature',401);
}
function tg_json($c,$method,$params=[]){
 foreach(['reply_markup','allowed_updates'] as $k){if(isset($params[$k])&&is_string($params[$k])){$d=json_decode($params[$k],true);if(json_last_error()===JSON_ERROR_NONE)$params[$k]=$d;}}
 $ch=curl_init('https://api.telegram.org/bot'.$c['bot_token'].'/'.$method);
 curl_setopt_array($ch,[
  CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_CONNECTTIMEOUT=>6,CURLOPT_TIMEOUT=>20,
  CURLOPT_IPRESOLVE=>CURL_IPRESOLVE_V4,CURLOPT_HTTP_VERSION=>CURL_HTTP_VERSION_1_1,
  CURLOPT_TCP_KEEPALIVE=>1,CURLOPT_NOSIGNAL=>1,
  CURLOPT_HTTPHEADER=>['Content-Type: application/json','Expect:','Connection: close'],
  CURLOPT_POSTFIELDS=>json_encode($params,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)
 ]);
 $body=curl_exec($ch);$err=curl_error($ch);$code=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);
 if($body===false||$err!=='')json_out(false,null,'Telegram cURL error: '.$err,502);
 $j=json_decode((string)$body,true);
 if(!is_array($j)||empty($j['ok']))json_out(false,null,$j['description']??('Telegram HTTP '.$code),502);
 return $j['result']??true;
}
function safe_filename($name){
 $name=basename($name);
 $name=preg_replace('/[^A-Za-z0-9._-]/','_',$name);
 return $name!==''?$name:'media.bin';
}
function multipart_body($method,$params,$filename,$mime,$bytes,&$content_type){
 $map=['sendPhoto'=>'photo','sendVoice'=>'voice','sendAudio'=>'audio','sendVideo'=>'video','sendDocument'=>'document'];
 if(!isset($map[$method]))json_out(false,null,'Media method not allowed',400);
 $boundary='----Mesora'.bin2hex(random_bytes(12));
 $eol="\r\n"; $body='';
 foreach($params as $key=>$value){
  if(is_array($value)||is_object($value))$value=json_encode($value,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
  if(is_bool($value))$value=$value?'true':'false';
  if($value===null)continue;
  $key=str_replace(['\\','"'],['\\\\','\"'],(string)$key);
  $body.='--'.$boundary.$eol.'Content-Disposition: form-data; name="'.$key.'"'.$eol.$eol.(string)$value.$eol;
 }
 $field=$map[$method];
 $filename=safe_filename($filename);
 $mime=preg_match('#^[a-z0-9.+-]+/[a-z0-9.+-]+$#i',$mime)?$mime:'application/octet-stream';
 $body.='--'.$boundary.$eol;
 $body.='Content-Disposition: form-data; name="'.$field.'"; filename="'.$filename.'"'.$eol;
 $body.='Content-Type: '.$mime.$eol.$eol;
 $body.=$bytes.$eol.'--'.$boundary.'--'.$eol;
 $content_type='multipart/form-data; boundary='.$boundary;
 return $body;
}
function telegram_media_send($c,$meta,$bytes){
 $method=(string)($meta['method']??'');
 $params=is_array($meta['params']??null)?$meta['params']:[];
 $name=(string)($meta['name']??'media.bin');
 $mime=(string)($meta['mime']??'application/octet-stream');
 $declared=(int)($meta['size']??0);
 if($declared!==strlen($bytes))json_out(false,null,'Media size mismatch',400);
 if($declared<1||$declared>8*1024*1024)json_out(false,null,'File too large',413);
 $want=(string)($meta['sha256']??'');
 if(!preg_match('/^[a-f0-9]{64}$/D',$want)||!hash_equals($want,hash('sha256',$bytes)))json_out(false,null,'Media checksum mismatch',400);

 $ctype=''; $multipart=multipart_body($method,$params,$name,$mime,$bytes,$ctype);
 $url='https://api.telegram.org/bot'.$c['bot_token'].'/'.$method;
 relay_log('info','telegram_media_send_start','Forwarding media to Telegram over IPv4/HTTP1.1',[
  'method'=>$method,'bytes'=>$declared,'multipart_bytes'=>strlen($multipart),'url_host'=>'api.telegram.org'
 ]);
 $ch=curl_init($url);
 curl_setopt_array($ch,[
  CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>$multipart,CURLOPT_RETURNTRANSFER=>true,
  CURLOPT_CONNECTTIMEOUT=>6,CURLOPT_TIMEOUT=>25,
  CURLOPT_IPRESOLVE=>CURL_IPRESOLVE_V4,CURLOPT_HTTP_VERSION=>CURL_HTTP_VERSION_1_1,
  CURLOPT_TCP_KEEPALIVE=>1,CURLOPT_NOSIGNAL=>1,
  CURLOPT_HTTPHEADER=>[
   'Content-Type: '.$ctype,
   'Content-Length: '.strlen($multipart),
   'Expect:',
   'Connection: close'
  ]
 ]);
 $started=microtime(true);
 $body=curl_exec($ch);
 $elapsed=round(microtime(true)-$started,3);
 $err=curl_error($ch);$errno=curl_errno($ch);
 $info=[
  'http'=>(int)curl_getinfo($ch,CURLINFO_HTTP_CODE),
  'primary_ip'=>(string)curl_getinfo($ch,CURLINFO_PRIMARY_IP),
  'namelookup'=>round((float)curl_getinfo($ch,CURLINFO_NAMELOOKUP_TIME),3),
  'connect'=>round((float)curl_getinfo($ch,CURLINFO_CONNECT_TIME),3),
  'appconnect'=>defined('CURLINFO_APPCONNECT_TIME')?round((float)curl_getinfo($ch,CURLINFO_APPCONNECT_TIME),3):null,
  'pretransfer'=>round((float)curl_getinfo($ch,CURLINFO_PRETRANSFER_TIME),3),
  'starttransfer'=>round((float)curl_getinfo($ch,CURLINFO_STARTTRANSFER_TIME),3),
  'total'=>round((float)curl_getinfo($ch,CURLINFO_TOTAL_TIME),3),
  'elapsed'=>$elapsed,
  'curl_errno'=>$errno
 ];
 curl_close($ch);
 $j=is_string($body)?json_decode($body,true):null;
 if($body===false||$err!==''||!is_array($j)||empty($j['ok'])){
  relay_log('error','telegram_media_send','Telegram media request failed',[
   'method'=>$method,'curl_error'=>$err,'timing'=>$info,
   'response'=>is_string($body)?substr($body,0,700):''
  ]);
  json_out(false,null,$j['description']??('Telegram media failed; cURL '.$errno.': '.$err),502);
 }
 relay_log('info','telegram_media_send','Telegram accepted media',[
  'method'=>$method,'bytes'=>$declared,'message_id'=>(int)($j['result']['message_id']??0),'timing'=>$info
 ]);
 json_out(true,$j['result']??true);
}
function telegram_media_download($c,$file_id){
 $f=tg_json($c,'getFile',['file_id'=>$file_id]);
 $fp=(string)($f['file_path']??'');
 if($fp==='')json_out(false,null,'File path missing',502);
 if((int)($f['file_size']??0)>8*1024*1024)json_out(false,null,'File too large',413);
 $ch=curl_init('https://api.telegram.org/file/bot'.$c['bot_token'].'/'.$fp);
 curl_setopt_array($ch,[
  CURLOPT_RETURNTRANSFER=>true,CURLOPT_CONNECTTIMEOUT=>10,CURLOPT_TIMEOUT=>45,CURLOPT_NOPROGRESS=>false,
  CURLOPT_PROGRESSFUNCTION=>function($ch,$total,$downloaded,$uploadTotal,$uploaded){return $downloaded>8*1024*1024?1:0;}
 ]);
 $bytes=curl_exec($ch);$err=curl_error($ch);$code=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);
 if($bytes===false||$err!==''||$code!==200)json_out(false,null,'File download failed: '.$err,502);
 if(strlen($bytes)>8*1024*1024)json_out(false,null,'File too large',413);
 relay_log('info','telegram_media_download','Telegram media streamed',['name'=>basename($fp),'bytes'=>strlen($bytes)]);
 http_response_code(200);
 header('Content-Type: application/octet-stream');
 header('Content-Length: '.strlen($bytes));
 header('X-Mesora-Filename: '.rawurlencode(basename($fp)));
 echo $bytes;
 exit;
}

$c=auth_config();
$method=(string)($_SERVER['REQUEST_METHOD']??'');
$raw=file_get_contents('php://input');
if(!is_string($raw))$raw='';

if($method==='PUT'){
 $action=(string)($_SERVER['HTTP_X_MESORA_ACTION']??'');
 $meta_header=(string)($_SERVER['HTTP_X_MESORA_META']??'');
 auth_headers($c,$raw,'PUT',$meta_header);

 if($action==='telegram_media_send'){
  if(strlen($raw)<1||strlen($raw)>8*1024*1024)json_out(false,null,'File too large',413);
  $meta=json_decode(rawurldecode($meta_header),true);
  if(!is_array($meta))json_out(false,null,'Invalid media metadata',400);
  telegram_media_send($c,$meta,$raw);
 }
 if($action==='telegram_media_download'){
  if($raw!=='')json_out(false,null,'Download request body must be empty',400);
  $meta=json_decode(rawurldecode($meta_header),true);
  $file_id=is_array($meta)?(string)($meta['file_id']??''):'';
  if($file_id==='')json_out(false,null,'file_id required',400);
  telegram_media_download($c,$file_id);
 }
 json_out(false,null,'Unknown PUT action',404);
}

if($method==='POST'&&(string)($_SERVER['HTTP_X_MESORA_ACTION']??'')==='telegram_media_send'){
 require_once __DIR__.'/queue-lib.php';
 $site=(string)($_SERVER['HTTP_X_MESORA_SITE']??'');$ts=(string)($_SERVER['HTTP_X_MESORA_TIMESTAMP']??'');$sig=(string)($_SERVER['HTTP_X_MESORA_SIGNATURE']??'');
 if($site===''||!hash_equals((string)($c['site_id']??''),$site)||!ctype_digit($ts)||abs(time()-(int)$ts)>180||$sig==='')json_out(false,null,'Unauthorized',401);
 $mf=$_FILES['meta_json']??null;$ff=$_FILES['media']??null;
 if(!is_array($mf)||(int)($mf['error']??UPLOAD_ERR_NO_FILE)!==UPLOAD_ERR_OK||!is_array($ff)||(int)($ff['error']??UPLOAD_ERR_NO_FILE)!==UPLOAD_ERR_OK)json_out(false,null,'Invalid multipart media payload',400);
 $meta_json=@file_get_contents((string)$mf['tmp_name']);$tmp=(string)$ff['tmp_name'];
 if(!is_string($meta_json)||$meta_json===''||!is_file($tmp))json_out(false,null,'Cannot read multipart media payload',400);
 if((int)($ff['size']??0)>8*1024*1024)json_out(false,null,'File too large',413);
 $media_hash=hash_file('sha256',$tmp);
 $want=hash_hmac('sha256',$ts.'.MEDIA.'.hash('sha256',$meta_json).'.'.$media_hash,(string)$c['shared_secret']);
 if(!hash_equals($want,$sig))json_out(false,null,'Bad media signature',401);
 $meta=json_decode($meta_json,true);if(!is_array($meta))json_out(false,null,'Invalid media metadata',400);
 if(!hash_equals((string)($meta['sha256']??''),$media_hash))json_out(false,null,'Media checksum mismatch',400);
 $id=mq_enqueue_media($meta,$tmp);if(!$id)json_out(false,null,'Relay queue storage failed',500);
 json_out(true,['queued'=>true,'job_id'=>$id]);
}

if($method!=='POST')json_out(false,null,'POST or PUT required',405);
auth_headers($c,$raw,'POST');
$in=json_decode($raw,true);
if(!is_array($in))json_out(false,null,'Invalid JSON',400);
$a=(string)($in['action']??'');


function wa_cfg($c){
 $token=(string)($c['whatsapp_access_token']??'');$phone=(string)($c['whatsapp_phone_number_id']??'');$ver=(string)($c['whatsapp_graph_version']??'v26.0');
 if($token===''||$phone==='')json_out(false,null,'WhatsApp Relay is not configured',503);
 return [$token,$phone,preg_match('/^v\d+\.\d+$/',$ver)?$ver:'v26.0'];
}
function wa_request($c,$method,$path,$payload=null){
 [$token,$phone,$ver]=wa_cfg($c);$url='https://graph.facebook.com/'.$ver.'/'.($path===''?rawurlencode($phone):$path);$ch=curl_init($url);$headers=['Authorization: Bearer '.$token,'Accept: application/json'];$opts=[CURLOPT_RETURNTRANSFER=>true,CURLOPT_CONNECTTIMEOUT=>8,CURLOPT_TIMEOUT=>25,CURLOPT_IPRESOLVE=>CURL_IPRESOLVE_V4,CURLOPT_HTTP_VERSION=>CURL_HTTP_VERSION_1_1,CURLOPT_HTTPHEADER=>$headers];
 if($method==='POST'){$headers[]='Content-Type: application/json';$opts[CURLOPT_POST]=true;$opts[CURLOPT_POSTFIELDS]=json_encode($payload,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);$opts[CURLOPT_HTTPHEADER]=$headers;}
 curl_setopt_array($ch,$opts);$body=curl_exec($ch);$err=curl_error($ch);$code=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);$j=is_string($body)?json_decode($body,true):null;if($body===false||$err!==''||$code<200||$code>=300)return ['_error'=>$err?:('WhatsApp HTTP '.$code),'_body'=>is_string($body)?substr($body,0,700):'','_code'=>$code];return is_array($j)?$j:true;
}
if($a==='telegram_health')json_out(true,tg_json($c,'getMe',[]));
if($a==='whatsapp_health'){[$wt,$wp,$wv]=wa_cfg($c);$r=wa_request($c,'GET',rawurlencode($wp).'?fields=id,display_phone_number,verified_name');if(is_array($r)&&isset($r['_error']))json_out(false,null,$r['_error'],502);json_out(true,['ok'=>true,'number'=>$r['display_phone_number']??'','name'=>$r['verified_name']??'']);}
if($a==='whatsapp_send'){$payload=is_array($in['payload']??null)?$in['payload']:null;if(!$payload)json_out(false,null,'WhatsApp payload missing',400);[$wt,$wp,$wv]=wa_cfg($c);$r=wa_request($c,'POST',rawurlencode($wp).'/messages',$payload);if(is_array($r)&&isset($r['_error']))json_out(false,null,$r['_error'],502);json_out(true,$r);}
if($a==='relay_media_logs')json_out(true,['logs'=>relay_log_tail((int)($in['limit']??120))]);
if($a==='telegram_call'){
 $m=(string)($in['method']??'');
 $allowed=['getMe','sendMessage','getFile','answerCallbackQuery','editMessageReplyMarkup','editMessageText','createForumTopic','closeForumTopic','reopenForumTopic'];
 if(!in_array($m,$allowed,true))json_out(false,null,'Method not allowed',400);
 json_out(true,tg_json($c,$m,is_array($in['params']??null)?$in['params']:[]));
}
if($a==='set_webhook'){
 $host=(string)($_SERVER['HTTP_HOST']??'');
 if($host==='')json_out(false,null,'Relay host missing',500);
 $path=rtrim(dirname($_SERVER['SCRIPT_NAME']??'/'),'/');
 if($path==='.'||$path==='/')$path='';
 $url='https://'.$host.$path.'/webhook.php';
 json_out(true,tg_json($c,'setWebhook',['url'=>$url,'secret_token'=>(string)$c['webhook_secret'],'allowed_updates'=>['message','callback_query']]));
}
json_out(false,null,'Unknown action',404);
