<?php
// Twilio webhook endpoint
// Flow: Ringba -> Twilio -> Gather ZIP -> Ping SD -> Accept SD -> Dial SD buyer OR fallback to Ringba

$config = require __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../sd/ServiceDirectClient.php';

header('Content-Type: text/xml; charset=UTF-8');

function twiml(string $xmlBody): void {
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
    echo "<Response>$xmlBody</Response>";
    exit;
}

function log_line(string $file, string $line): void {
    $ts = date('Y-m-d H:i:s');
    @file_put_contents($file, "[$ts] $line\n", FILE_APPEND);
}

function normalize_phone(?string $num): string {
    $num = trim((string)$num);
    if ($num === '') return '';

    // keep digits and plus
    $clean = preg_replace('/[^0-9\+]/', '', $num);

    // If already E.164 with +, keep it as-is
    if ($clean !== '' && $clean[0] === '+') {
        return $clean;
    }

    // Only digits now
    $digits = preg_replace('/\D/', '', $clean);

    // US 10-digit -> +1XXXXXXXXXX
    if (strlen($digits) === 10) {
        return '+1' . $digits;
    }

    // US 11-digit starting with 1 -> +1XXXXXXXXXX
    if (strlen($digits) === 11 && $digits[0] === '1') {
        return '+' . $digits;
    }

    // last resort
    return ($digits !== '') ? ('+' . $digits) : '';
}

/**
 * SD Ping acceptance:
 * SD Ping returns { data: { available_buyer: true/false, request_id, bid, ... } }
 */
function sd_ping_available(?array $j): bool {
    if (!is_array($j)) return false;
    return (bool)($j['data']['available_buyer'] ?? false);
}

function sd_ping_request_id(?array $j): ?int {
    if (!is_array($j)) return null;
    $rid = $j['data']['request_id'] ?? null;
    if (is_int($rid)) return $rid;
    if (is_string($rid) && ctype_digit($rid)) return (int)$rid;
    return null;
}

/**
 * SD Accept returns: { data: { phone_number: "5555555555" } }
 */
function sd_accept_phone(?array $j): ?string {
    if (!is_array($j)) return null;
    $p = $j['data']['phone_number'] ?? null;
    if (!is_string($p) || trim($p) === '') return null;
    return normalize_phone($p);
}

/**
 * Fallback dial: send the caller back into Ringba (a re-entry DID)
 */
function fallbackDial(array $vertCfg, int $dialTimeout): string
{
    $fallback = normalize_phone($vertCfg['fallback_ringba_number'] ?? '');
    if ($fallback === '') {
        return "<Hangup/>";
    }
    $fbEsc = htmlspecialchars($fallback, ENT_QUOTES, 'UTF-8');
    return "<Dial timeout=\"{$dialTimeout}\">{$fbEsc}</Dial>";
}

// ---- validate vertical
$vertical = $_GET['vertical'] ?? '';
$vertical = is_string($vertical) ? trim($vertical) : '';
if ($vertical === '' || !isset($config['verticals'][$vertical])) {
    log_line($config['logs']['twilio'], "ERROR: invalid vertical. query=" . json_encode($_GET));
    twiml('<Say>Configuration error. Please try again later.</Say><Hangup/>');
}

$vertCfg = $config['verticals'][$vertical];

$step = $_GET['step'] ?? 'gather';
$step = is_string($step) ? $step : 'gather';

$from    = normalize_phone($_POST['From'] ?? '');
$to      = normalize_phone($_POST['To'] ?? '');
$callSid = (string)($_POST['CallSid'] ?? '');

log_line($config['logs']['twilio'], "INCOMING callSid={$callSid} vertical={$vertical} from={$from} to={$to} step={$step}");

$voice         = htmlspecialchars($config['twilio']['voice']['voice'], ENT_QUOTES, 'UTF-8');
$lang          = htmlspecialchars($config['twilio']['voice']['language'], ENT_QUOTES, 'UTF-8');
$gatherTimeout = (int)$config['twilio']['timeouts']['gather'];
$dialTimeout   = (int)$config['twilio']['timeouts']['dial'];

// Build absolute base URL for Twilio action callbacks (safer than relative)
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host   = $_SERVER['HTTP_HOST'] ?? '';
$basePath = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '')), '/');
$baseUrl  = $scheme . '://' . $host . $basePath;

// --- STEP 1: ask zip
if ($step === 'gather') {
    $actionUrl = $baseUrl . "/voice.php?step=lookup&vertical=" . rawurlencode($vertical);
    $actionEsc = htmlspecialchars($actionUrl, ENT_QUOTES, 'UTF-8');

    $xml = ''
        . "<Gather input=\"dtmf\" numDigits=\"5\" timeout=\"{$gatherTimeout}\" action=\"{$actionEsc}\" method=\"POST\">"
        . "<Say voice=\"{$voice}\" language=\"{$lang}\">Please enter your 5 digit zip code.</Say>"
        . "</Gather>"
        . "<Say voice=\"{$voice}\" language=\"{$lang}\">We did not receive a zip code. Please hold while we connect you.</Say>"
        . fallbackDial($vertCfg, $dialTimeout);

    twiml($xml);
}

// --- STEP 2: do SD ping + accept
if ($step === 'lookup') {
    $digits = (string)($_POST['Digits'] ?? '');
    $digits = preg_replace('/\D/', '', $digits);

    if (strlen($digits) !== 5) {
        log_line($config['logs']['twilio'], "ZIP_INVALID callSid={$callSid} digits={$digits}");
        $xml = "<Say voice=\"{$voice}\" language=\"{$lang}\">That zip code seems invalid.</Say>"
             . fallbackDial($vertCfg, $dialTimeout);
        twiml($xml);
    }

    $sd = new ServiceDirectClient($config);

    $source           = (string)($config['traffic_source'] ?? '');
    $service_category = (string)($vertCfg['service_category'] ?? '');
    $isTest           = ($config['environment'] === 'test');

    // IMPORTANT:
    // caller_id_in_post must be boolean. If true, you MUST send caller_id in ACCEPT payload.
    $pingPayload = [
        'sd_api_key'        => $config['servicedirect']['api_key'],
        'zip_code'          => $digits,
        'service_category'  => $service_category,
        'source'            => $source,
        'caller_id'         => $from,     // ANI
        'caller_id_in_post' => true,      // promise caller_id will be sent in ACCEPT
        'test_mode'         => $isTest,
        'suppress_response_codes' => true, // keeps http 200; SD sets x-original-status-code
    ];

    $ping = $sd->ping($pingPayload);
    log_line($config['logs']['sd'], "PING callSid={$callSid} payload=" . json_encode($pingPayload) . " resp=" . json_encode($ping));

    $pingJson = $ping['json'] ?? null;

    if (!$ping['ok'] || !sd_ping_available($pingJson)) {
        log_line($config['logs']['sd'], "PING_REJECT callSid={$callSid} zip={$digits} vertical={$vertical} http={$ping['http_code']}");
        $xml = "<Say voice=\"{$voice}\" language=\"{$lang}\">Please hold while we connect you.</Say>"
             . fallbackDial($vertCfg, $dialTimeout);
        twiml($xml);
    }

    $requestId = sd_ping_request_id($pingJson);
    if ($requestId === null) {
        log_line($config['logs']['sd'], "PING_NO_REQUEST_ID callSid={$callSid} resp=" . json_encode($pingJson));
        $xml = "<Say voice=\"{$voice}\" language=\"{$lang}\">Please hold while we connect you.</Say>"
             . fallbackDial($vertCfg, $dialTimeout);
        twiml($xml);
    }

    // ACCEPT payload (send caller_id here because we set caller_id_in_post=true above)
    $acceptPayload = [
        'sd_api_key' => $config['servicedirect']['api_key'],
        'test_mode'  => $isTest,
        'caller_id'  => $from,     // MUST match ping caller_id if it was sent in ping
        'suppress_response_codes' => true,
    ];

    $accept = $sd->accept($requestId, $acceptPayload);
    log_line($config['logs']['sd'], "ACCEPT callSid={$callSid} request_id={$requestId} payload=" . json_encode($acceptPayload) . " resp=" . json_encode($accept));

    $dest = sd_accept_phone($accept['json'] ?? null);

    if (!$accept['ok'] || !$dest) {
        log_line($config['logs']['sd'], "ACCEPT_REJECT callSid={$callSid} request_id={$requestId} http={$accept['http_code']} json=" . json_encode($accept['json']));
        $xml = "<Say voice=\"{$voice}\" language=\"{$lang}\">Please hold while we connect you.</Say>"
             . fallbackDial($vertCfg, $dialTimeout);
        twiml($xml);
    }

    // Success: Dial SD/buyer destination
    // IMPORTANT: set callerId to the ANI so buyers don't see your Twilio DID.
    $destEsc = htmlspecialchars($dest, ENT_QUOTES, 'UTF-8');
    $callerIdEsc = htmlspecialchars($from, ENT_QUOTES, 'UTF-8');

    $xml = ""
        . "<Say voice=\"{$voice}\" language=\"{$lang}\">Connecting you now.</Say>"
        . "<Dial timeout=\"{$dialTimeout}\" callerId=\"{$callerIdEsc}\">{$destEsc}</Dial>";

    twiml($xml);
}

// default safety
twiml("<Say voice=\"{$voice}\" language=\"{$lang}\">Please hold while we connect you.</Say>" . fallbackDial($vertCfg, $dialTimeout));
