<?php
declare(strict_types=1);

/**
 * pusher-auth.php
 * Auth for Pusher private/presence channels.
 *
 * CHANNELS:
 * - private-room.{roomId}
 * - presence-room.{roomId}
 *
 * AUTH RULES:
 * - DM rooms OR scope_level='custom' => must be a member in pth_party_room_members
 * - scope rooms (polling_station/ward/constituency/county/national) => computed from users + room scope
 *
 * NOTES:
 * - This file only authenticates subscription. Posting permissions should be enforced in your send-message endpoint.
 * - Uses env vars for pusher keys.
 */

require_once __DIR__ . '/_cors.php';
header('Content-Type: application/json; charset=utf-8');

require_once __DIR__ . '/conn.php';             // provides $db (PDO)
require_once __DIR__ . '/../config/pusher.php'; // optional if you have helper, but we use env vars below

/** ---------- Helpers ---------- */
function respond(int $status, array $payload): void {
  http_response_code($status);
  echo json_encode($payload, JSON_UNESCAPED_SLASHES);
  exit;
}
function cookieNames(): array { return array_values(array_keys($_COOKIE ?? [])); }
function pickSessionName(): ?string {
  if (!empty($_COOKIE['PHPSESSADM'])) return 'PHPSESSADM';
  if (!empty($_COOKIE['PHPSESSCLI'])) return 'PHPSESSCLI';
  if (!empty($_COOKIE['PHPSESSAFF'])) return 'PHPSESSAFF';
  return null;
}

/** ---------- Roles (adjust to your exact values in users.role) ---------- */
function roleIs(array $user, array $roles): bool {
  $r = strtolower((string)($user['role'] ?? ''));
  foreach ($roles as $x) if ($r === strtolower($x)) return true;
  return false;
}
function isSuperAdmin(array $u): bool { return roleIs($u, ['super_admin','superadmin']); }
function isNational(array $u): bool { return roleIs($u, ['national','national_leader']); }
function isCounty(array $u): bool { return roleIs($u, ['county','county_rep']); }
function isConstituency(array $u): bool { return roleIs($u, ['constituency','constituency_rep']); }
function isWard(array $u): bool { return roleIs($u, ['ward','ward_rep']); }

function isMember(PDO $db, int $roomId, int $userId): bool {
  $st = $db->prepare("SELECT 1 FROM pth_party_room_members WHERE room_id=:rid AND user_id=:uid LIMIT 1");
  $st->execute([':rid'=>$roomId, ':uid'=>$userId]);
  return (bool)$st->fetchColumn();
}

function canAccessScopeRoom(array $user, array $room): bool {
  if (isSuperAdmin($user)) return true;
  if (isNational($user)) return true;

  $scope = (string)($room['scope_level'] ?? '');

  // POLICY: everyone can subscribe to national rooms (recommended for “ops”).
  // If you want leadership-only, change to: return false;
  if ($scope === 'national') return true;

  $uCounty = (int)($user['county_id'] ?? 0);
  $uConst  = (int)($user['constituency_id'] ?? 0);
  $uWard   = (int)($user['ward_id'] ?? 0);
  $uPs     = (int)($user['polling_station_id'] ?? 0);

  $rCounty = (int)($room['county_id'] ?? 0);
  $rConst  = (int)($room['constituency_id'] ?? 0);
  $rWard   = (int)($room['ward_id'] ?? 0);
  $rPs     = (int)($room['polling_station_id'] ?? 0);

  if ($scope === 'county') return $uCounty > 0 && $uCounty === $rCounty;

  if ($scope === 'constituency') {
    if (isCounty($user)) return $uCounty > 0 && $uCounty === $rCounty;
    return $uConst > 0 && $uConst === $rConst;
  }

  if ($scope === 'ward') {
    if (isCounty($user)) return $uCounty > 0 && $uCounty === $rCounty;
    if (isConstituency($user)) return $uConst > 0 && $uConst === $rConst;
    return $uWard > 0 && $uWard === $rWard;
  }

  if ($scope === 'polling_station') {
    if (isCounty($user)) return $uCounty > 0 && $uCounty === $rCounty;
    if (isConstituency($user)) return $uConst > 0 && $uConst === $rConst;
    if (isWard($user)) return $uWard > 0 && $uWard === $rWard;
    return $uPs > 0 && $uPs === $rPs;
  }

  return false;
}

/** ---------- Session ---------- */
$sessionName = pickSessionName();
if (!$sessionName) {
  respond(401, [
    'error' => 'Not authenticated (no valid session cookie)',
    'cookie_names' => cookieNames(),
    'hint' => 'Frontend must send cookies (fetch: credentials:"include" / axios: withCredentials:true).',
  ]);
}
session_name($sessionName);
session_start();

$userId = (int)($_SESSION['user_id'] ?? 0);
if ($userId <= 0) $userId = (int)($_SESSION['SESS_MEMBER_ID'] ?? 0);
if ($userId <= 0) respond(401, ['error' => 'Not authenticated']);

/** ---------- Input (POST form OR JSON) ---------- */
$socketId = $_POST['socket_id'] ?? null;
$channelName = $_POST['channel_name'] ?? null;

if (!$socketId || !$channelName) {
  $raw = file_get_contents('php://input');
  if ($raw) {
    $data = json_decode($raw, true);
    if (is_array($data)) {
      $socketId = $socketId ?: ($data['socket_id'] ?? null);
      $channelName = $channelName ?: ($data['channel_name'] ?? null);
    }
  }
}
if (!$socketId || !$channelName) respond(400, ['error' => 'Missing socket_id or channel_name']);

/** ---------- Channel validation ---------- */
$roomId = null;
$isPresence = false;

if (preg_match('/^private-room\.(\d+)$/', (string)$channelName, $m)) {
  $roomId = (int)$m[1];
} elseif (preg_match('/^presence-room\.(\d+)$/', (string)$channelName, $m)) {
  $roomId = (int)$m[1];
  $isPresence = true;
} else {
  respond(403, ['error' => 'Invalid channel']);
}

/** ---------- Load room ---------- */
$roomStmt = $db->prepare("
  SELECT id, room_type, room_key, room_name, scope_level,
         county_id, constituency_id, ward_id, polling_station_id, party_id
  FROM pth_party_room
  WHERE id = :rid
  LIMIT 1
");
$roomStmt->execute([':rid'=>$roomId]);
$room = $roomStmt->fetch(PDO::FETCH_ASSOC);
if (!$room) respond(404, ['error' => 'Room not found']);

/** ---------- Load user context ---------- */
$userStmt = $db->prepare("
  SELECT id, role, name, county_id, constituency_id, ward_id, polling_station_id
  FROM users
  WHERE id = :uid
  LIMIT 1
");
$userStmt->execute([':uid'=>$userId]);
$user = $userStmt->fetch(PDO::FETCH_ASSOC);
if (!$user) respond(401, ['error' => 'User not found']);

/** ---------- Auto-heal DM membership (optional, but helps) ---------- */
if (($room['room_type'] ?? '') === 'dm') {
  $rk = (string)($room['room_key'] ?? '');
  if (str_starts_with($rk, 'dm:')) {
    $pair = substr($rk, 3);
    if (strpos($pair, '_') !== false) {
      [$aStr, $bStr] = explode('_', $pair, 2);
      $a = (int)$aStr; $b = (int)$bStr;
      if ($a > 0 && $b > 0) {
        $ins = $db->prepare("
          INSERT IGNORE INTO pth_party_room_members (room_id, user_id, role)
          VALUES (:rid, :uid, 'member')
        ");
        $ins->execute([':rid'=>$roomId, ':uid'=>$a]);
        $ins->execute([':rid'=>$roomId, ':uid'=>$b]);
      }
    }
  }
}

/** ---------- Authorization ---------- */
$roomType = (string)($room['room_type'] ?? '');
$scopeLevel = (string)($room['scope_level'] ?? '');

$allowed = false;

if ($roomType === 'dm' || $scopeLevel === 'custom') {
  $allowed = isMember($db, $roomId, $userId);
} else {
  $allowed = canAccessScopeRoom($user, $room);
}

if (!$allowed) respond(403, ['error' => 'Not allowed']);

/** ---------- Pusher credentials ---------- */
$appid   = getenv('PUSHER_APP_ID') ?: '';
$key     = getenv('PUSHER_KEY') ?: '';
$secret  = getenv('PUSHER_SECRET') ?: '';
$cluster = getenv('PUSHER_CLUSTER') ?: 'mt1';

if ($appid === '' || $key === '' || $secret === '') {
  respond(500, [
    'error' => 'Pusher credentials missing',
    'hint'  => 'Set env vars: PUSHER_APP_ID, PUSHER_KEY, PUSHER_SECRET, PUSHER_CLUSTER',
  ]);
}

require_once __DIR__ . '/../../vendor/autoload.php';
$pusher = new Pusher\Pusher($key, $secret, $appid, [
  'cluster' => $cluster,
  'useTLS'  => true,
]);

/**
 * For presence channels, return presence auth with user info.
 * This enables member list / online list in the channel.
 */
if ($isPresence) {
  $presenceData = [
    'user_id' => (string)$userId,
    'user_info' => [
      'name' => (string)($user['name'] ?? 'User'),
      'role' => (string)($user['role'] ?? ''),
    ],
  ];
  echo $pusher->presence_auth((string)$channelName, (string)$socketId, $presenceData['user_id'], $presenceData['user_info']);
  exit;
}

/** Private channel auth */
echo $pusher->socket_auth((string)$channelName, (string)$socketId);
