<?php
declare(strict_types=1);

/**
 * OpenAI Realtime SIP Webhook — single-file
 * Receives:  realtime.call.incoming (JSON POST)
 * Responds:  POST /v1/realtime/calls/{call_id}/accept to start the voice session
 *
 * Place on an HTTPS URL (e.g., https://your-domain.com/openai-webhook.php)
 * Make sure php-curl is installed and the server can reach api.openai.com:443
 */

/* ================== CONFIG (EDIT) ================== */
$OPENAI_API_KEY    = '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';        // Your OpenAI API key
$OPENAI_PROJECT_ID = 'proj_4uT5E0Vsxl0HhkSG6q5Ysotq';      // MUST match the Project ID you dial in the SIP URI

$MODEL        = 'gpt-4o-mini-realtime';      // Realtime model
$VOICE        = 'alloy';                     // Voice name
$VOICE_SPEED  = 1.0;                         // 0.8–1.2 typical
$INSTRUCTIONS = 'You are CallChex Voice Agent. Be concise; greet fast; verify last 4 if asked; explain CallChex scoring & compliance summaries.';

/* Primary desired log path (we’ll fall back automatically if not writable): */
$LOG_CANDIDATES = [
  '/tmp/openai_realtime_webhook.log',
  '/var/www/openai-logs/openai_realtime_webhook.log',   // create this dir if you prefer
  __DIR__ . '/openai_realtime_webhook.log',             // last resort in webroot
];

$SHARED_TOKEN = ''; // optional: require header X-Webhook-Token: <this>
/* ================== */

function pick_log_path(array $candidates): string {
  foreach ($candidates as $p) {
    $dir = dirname($p);
    if (!is_dir($dir)) { @mkdir($dir, 0750, true); }
    if (is_dir($dir) && is_writable($dir)) return $p;
  }
  return ''; // will use error_log()
}
$LOG_FILE = pick_log_path($LOG_CANDIDATES);

function log_line(string $msg): void {
  global $LOG_FILE;
  $line = date('c').' '.$msg."\n";
  if ($LOG_FILE !== '') {
    @file_put_contents($LOG_FILE, $line, FILE_APPEND);
  } else {
    error_log("[openai-webhook] ".$msg);
  }
}
function respond(int $code, array $json): void {
  global $LOG_FILE;
  http_response_code($code);
  header('Content-Type: application/json');
  if ($LOG_FILE !== '') header('X-Log-Path: '.$LOG_FILE);
  echo json_encode($json);
  exit;
}
function all_headers(): array {
  if (function_exists('getallheaders')) return getallheaders();
  $h = [];
  foreach ($_SERVER as $k => $v) if (strpos($k,'HTTP_')===0) {
    $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_',' ',substr($k,5)))));
    $h[$name] = $v;
  }
  return $h;
}

/* Health/method */
if ($_SERVER['REQUEST_METHOD']==='GET' || $_SERVER['REQUEST_METHOD']==='HEAD') {
  header('Content-Type: text/plain');
  if ($LOG_FILE !== '') header('X-Log-Path: '.$LOG_FILE);
  echo "OK\n"; exit;
}
if ($_SERVER['REQUEST_METHOD']!=='POST') {
  header('Allow: POST, GET, HEAD');
  respond(405, ['error'=>'method_not_allowed']);
}

/* Optional shared secret */
if ($SHARED_TOKEN !== '') {
  $hdr = $_SERVER['HTTP_X_WEBHOOK_TOKEN'] ?? '';
  if (!hash_equals($SHARED_TOKEN, $hdr)) {
    log_line("Unauthorized: bad X-Webhook-Token");
    respond(401, ['error'=>'unauthorized']);
  }
}

/* Config check */
if ($OPENAI_API_KEY==='' || $OPENAI_PROJECT_ID==='') {
  log_line("Config error: missing OPENAI_API_KEY or OPENAI_PROJECT_ID");
  respond(500, ['error'=>'server_misconfigured']);
}

/* Read body */
$raw = file_get_contents('php://input') ?: '';
$event = json_decode($raw, true);
log_line("WEBHOOK HIT UA=".($_SERVER['HTTP_USER_AGENT'] ?? 'n/a'));
log_line("HEADERS=".json_encode(all_headers()));
log_line("BODY=".$raw);

if (json_last_error()!==JSON_ERROR_NONE) {
  log_line("Invalid JSON payload");
  respond(400, ['error'=>'invalid_json']);
}

/* Only handle realtime.call.incoming */
$type = $event['type'] ?? '';
if ($type !== 'realtime.call.incoming') {
  log_line("Ignoring event type: ".$type);
  http_response_code(204); exit;
}

$callId = $event['data']['id'] ?? $event['data']['call_id'] ?? null;
if (!$callId) {
  log_line("Missing call_id in payload");
  respond(400, ['error'=>'missing_call_id']);
}
log_line("Incoming call_id=".$callId);

/* Build accept payload */
$acceptPayload = [
  'session' => [
    'type'         => 'realtime',
    'model'        => $MODEL,
    'instructions' => $INSTRUCTIONS,
    'audio'        => [ 'output' => [ 'voice' => $VOICE, 'speed' => $VOICE_SPEED ] ],
  ],
];

/* POST /accept */
$acceptUrl = 'https://api.openai.com/v1/realtime/calls/'.rawurlencode($callId).'/accept';
$headers = [
  'Authorization: Bearer '.$OPENAI_API_KEY,
  'OpenAI-Project: '.$OPENAI_PROJECT_ID,
  'Content-Type: application/json',
];

/* Ensure curl is present */
if (!function_exists('curl_init')) {
  log_line("ERROR: php-curl missing");
  respond(500, ['error'=>'php_curl_missing']);
}

$ch = curl_init($acceptUrl);
curl_setopt_array($ch, [
  CURLOPT_POST           => true,
  CURLOPT_HTTPHEADER     => $headers,
  CURLOPT_POSTFIELDS     => json_encode($acceptPayload),
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CONNECTTIMEOUT => 5,
  CURLOPT_TIMEOUT        => 15,
]);
$respBody = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr  = curl_error($ch);
curl_close($ch);

if ($respBody === false) {
  log_line("ACCEPT transport error: ".$curlErr);
  respond(502, ['error'=>'accept_transport_error', 'detail'=>$curlErr]);
}
log_line("ACCEPT HTTP $httpCode BODY=".$respBody);

if ($httpCode >= 200 && $httpCode < 300) {
  respond(200, ['status'=>'accepted','call_id'=>$callId,'log'=>$LOG_FILE?:'error_log']);
}

if ($httpCode === 401) respond(502, ['error'=>'accept_unauthorized']);
if ($httpCode === 403) respond(502, ['error'=>'accept_forbidden_project_mismatch']);
if ($httpCode === 404) respond(502, ['error'=>'accept_call_not_found_or_expired']);
if ($httpCode === 409) respond(502, ['error'=>'accept_conflict_already_handled']);
respond(502, ['error'=>'accept_http_'.$httpCode]);
