<?php
/**
 * MegaMatrix ↔ FreePBX call-recordings bridge.
 *
 * Put this file in the FreePBX web root on the PBX (10.10.2.11):
 *     /var/www/html/recordings.php
 * It becomes reachable through the existing tunnel:
 *     https://pbx.megatrust.live/recordings.php
 *
 * Two modes:
 *   1) List:  ?agent_username=&extension=20&date=2026-07-16&region=egypt
 *             -> JSON { ok, recordings:[ {callId,caller,callee,extension,startedAt,durationSeconds,audioUrl,downloadUrl} ] }
 *   2) Serve: ?file=<relative-path>&dl=0|1   -> streams the .wav/.mp3 for playback/download
 *
 * Security: every request must carry the shared token, either as
 *   Authorization: Bearer <TOKEN>   (the app sends this for the list)
 *   or ?token=<TOKEN>               (used inside audioUrl for the browser)
 */

// ==== EDIT THIS ONE LINE ====
$TOKEN = 'MegaTrustRec_2026_ChangeMe';   // must match PBX_RECORDINGS_API_TOKEN in the app
// ============================

$MONITOR = '/var/spool/asterisk/monitor';   // FreePBX default recordings folder

header('Access-Control-Allow-Origin: *');

/* ---------- auth ---------- */
function bearer_token() {
    $auth = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if (!$auth && function_exists('apache_request_headers')) {
        $h = apache_request_headers();
        foreach ($h as $k => $v) { if (strtolower($k) === 'authorization') { $auth = $v; break; } }
    }
    return preg_replace('/^Bearer\s+/i', '', trim($auth));
}
$provided = $_GET['token'] ?? bearer_token();
if ($TOKEN !== '' && !hash_equals($TOKEN, (string) $provided)) {
    http_response_code(401);
    header('Content-Type: application/json');
    echo json_encode(['ok' => false, 'error' => 'unauthorized']);
    exit;
}

/* ---------- read FreePBX DB credentials ---------- */
function freepbx_db() {
    $c = ['host' => '127.0.0.1', 'user' => 'freepbxuser', 'pass' => '', 'cdr' => 'asteriskcdrdb'];
    foreach (['/etc/freepbx.conf', '/etc/amportal.conf'] as $f) {
        if (!is_readable($f)) continue;
        $s = file_get_contents($f);
        // /etc/freepbx.conf  ->  $amp_conf['AMPDBPASS'] = 'xxx';
        // /etc/amportal.conf ->  AMPDBPASS=xxx
        if (preg_match("/AMPDBUSER['\"\]\s=]+['\"]?([A-Za-z0-9_.\-]+)/i", $s, $m)) $c['user'] = $m[1];
        if (preg_match("/AMPDBPASS['\"\]\s=]+['\"]?([^'\"\s;#]+)/i", $s, $m)) $c['pass'] = $m[1];
        if (preg_match("/AMPDBHOST['\"\]\s=]+['\"]?([A-Za-z0-9_.\-]+)/i", $s, $m)) $c['host'] = $m[1];
    }
    // The CDR database in FreePBX is always 'asteriskcdrdb' (same MySQL user).
    $c['cdr'] = 'asteriskcdrdb';
    return $c;
}

/* ---------- resolve a recording file safely under $MONITOR ---------- */
function find_recording($MONITOR, $recordingfile, $calldate) {
    $base = basename((string) $recordingfile);          // never trust the path
    if ($base === '') return null;
    // FreePBX stores under monitor/YYYY/MM/DD/<file>
    $sub = $calldate ? date('Y/m/d', strtotime($calldate)) : '';
    $candidates = [];
    if ($sub) $candidates[] = "$MONITOR/$sub/$base";
    $candidates[] = "$MONITOR/$base";
    foreach ($candidates as $p) {
        $real = realpath($p);
        if ($real && strpos($real, realpath($MONITOR)) === 0 && is_file($real)) return $real;
    }
    // last resort: shallow recursive search by basename
    $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($MONITOR, FilesystemIterator::SKIP_DOTS));
    foreach ($it as $file) {
        if ($file->getFilename() === $base) return $file->getPathname();
    }
    return null;
}

/* ==================== MODE 2: serve an audio file ==================== */
if (isset($_GET['file'])) {
    $rel  = $_GET['file'];
    $real = find_recording($MONITOR, $rel, $_GET['d'] ?? null);
    if (!$real) { http_response_code(404); echo 'not found'; exit; }

    $ext  = strtolower(pathinfo($real, PATHINFO_EXTENSION));
    $mime = $ext === 'mp3' ? 'audio/mpeg' : ($ext === 'gsm' ? 'audio/gsm' : 'audio/wav');
    $size = filesize($real);

    header('Content-Type: ' . $mime);
    header('Accept-Ranges: bytes');
    if (!empty($_GET['dl'])) header('Content-Disposition: attachment; filename="' . basename($real) . '"');

    // basic HTTP Range support so playback seeking works
    if (isset($_SERVER['HTTP_RANGE']) && preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $m)) {
        $start = (int) $m[1];
        $end   = $m[2] === '' ? $size - 1 : (int) $m[2];
        $len   = $end - $start + 1;
        http_response_code(206);
        header("Content-Range: bytes $start-$end/$size");
        header('Content-Length: ' . $len);
        $fp = fopen($real, 'rb');
        fseek($fp, $start);
        echo fread($fp, $len);
        fclose($fp);
    } else {
        header('Content-Length: ' . $size);
        readfile($real);
    }
    exit;
}

/* ==================== MODE 1: list recordings ==================== */
header('Content-Type: application/json');

$extension = preg_replace('/\D/', '', $_GET['extension'] ?? '');
$date      = preg_replace('/[^0-9\-]/', '', $_GET['date'] ?? date('Y-m-d'));

if ($extension === '') { echo json_encode(['ok' => true, 'recordings' => []]); exit; }

$db = freepbx_db();
try {
    $pdo = new PDO(
        "mysql:host={$db['host']};dbname={$db['cdr']};charset=utf8",
        $db['user'], $db['pass'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
} catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(['ok' => false, 'error' => 'DB connect failed: ' . $e->getMessage()]);
    exit;
}

$like = '%/' . $extension . '-%';
$stmt = $pdo->prepare(
    "SELECT uniqueid, calldate, src, dst, duration, billsec, disposition, recordingfile
       FROM cdr
      WHERE recordingfile <> ''
        AND DATE(calldate) = :d
        AND (src = :e OR dst = :e OR channel LIKE :l OR dstchannel LIKE :l)
      ORDER BY calldate DESC
      LIMIT 500"
);
$stmt->execute([':d' => $date, ':e' => $extension, ':l' => $like]);

$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'https';
$self   = $scheme . '://' . ($_SERVER['HTTP_HOST'] ?? 'pbx.megatrust.live') . ($_SERVER['SCRIPT_NAME'] ?? '/recordings.php');

$out = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
    $base = basename((string) $r['recordingfile']);
    if ($base === '') continue;
    $q = http_build_query(['token' => $TOKEN, 'file' => $base, 'd' => $r['calldate']]);
    $out[] = [
        'callId'          => $r['uniqueid'],
        'caller'          => $r['src'],
        'callee'          => $r['dst'],
        'extension'       => $extension,
        'startedAt'       => $r['calldate'],
        // durationSeconds = ACTUAL talk time (billsec). A no-answer call has
        // billsec = 0 and an empty recording, so the app filters those out.
        'durationSeconds' => (int) $r['billsec'],
        'billsecSeconds'  => (int) $r['billsec'],
        'callSeconds'     => (int) $r['duration'],   // total incl. ring, for reference
        'disposition'     => $r['disposition'],
        'audioUrl'        => "$self?$q",
        'downloadUrl'     => "$self?$q&dl=1",
    ];
}

echo json_encode(['ok' => true, 'count' => count($out), 'recordings' => $out]);
