<?php
declare(strict_types=1);

header('Content-Type: application/json');

$cfg = require __DIR__ . '/../config.php';

$tenantsDir = rtrim((string)($cfg['tenants_dir'] ?? '/home/mytown/blog.mytown.ink/tenants'), '/');
$logFile    = $tenantsDir . '/_yoco_webhook.log';

function logit(string $msg): void {
  global $logFile;
  $line = '[' . date('c') . '] ' . $msg . PHP_EOL;
  @file_put_contents($logFile, $line, FILE_APPEND);
  error_log('YOCO_WEBHOOK: ' . $msg);
}

function read_json(string $path): ?array {
  if (!is_file($path)) return null;
  $raw = file_get_contents($path);
  if ($raw === false) return null;
  $d = json_decode($raw, true);
  return is_array($d) ? $d : null;
}

function write_json_atomic(string $path, array $data, int $mode = 0640): bool {
  $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  if ($json === false) return false;
  $tmp = $path . '.tmp';
  if (file_put_contents($tmp, $json, LOCK_EX) === false) return false;
  @chmod($tmp, $mode);
  return @rename($tmp, $path);
}

function ensure_dir(string $dir, int $mode = 0750): bool {
  if (is_dir($dir)) return true;
  if (!@mkdir($dir, $mode, true)) return false;
  @chmod($dir, $mode);
  return true;
}

function env_get(string $envPath, string $key): string {
  if (!is_file($envPath)) return '';
  $env = file_get_contents($envPath) ?: '';
  if (preg_match('/^' . preg_quote($key, '/') . '=(.*)$/m', $env, $m)) {
    return trim($m[1]);
  }
  return '';
}

function env_set_if_missing(string $envPath, array $pairs, int $mode = 0640): void {
  $existing = is_file($envPath) ? (file_get_contents($envPath) ?: '') : '';
  $lines = $existing === '' ? [] : preg_split("/\r\n|\n|\r/", $existing);

  $present = [];
  foreach ($lines as $line) {
    $pos = strpos((string)$line, '=');
    if ($pos !== false) $present[substr((string)$line, 0, $pos)] = true;
  }

  foreach ($pairs as $k => $v) {
    if (!isset($present[$k])) $lines[] = $k . '=' . $v;
  }

  $out = rtrim(implode("\n", array_filter($lines, fn($x) => $x !== ''))) . "\n";
  @file_put_contents($envPath, $out);
  @chmod($envPath, $mode);
}

function headers_lower(): array {
  $out = [];
  foreach ($_SERVER as $k => $v) {
    if (str_starts_with($k, 'HTTP_')) {
      $name = strtolower(str_replace('_', '-', substr($k, 5)));
      $out[$name] = $v;
    }
  }
  return $out;
}

/**
 * Svix secrets are usually provided as: whsec_<base64>
 * The HMAC key is the decoded bytes of the base64 part.
 */
function svix_secret_bytes(string $secret) {
  if (str_starts_with($secret, 'whsec_')) {
    $b64 = substr($secret, 6);
    $decoded = base64_decode($b64, true);
    if ($decoded !== false) return $decoded; // raw bytes
    // If decode fails, fall back to the raw (sans prefix)
    return $b64;
  }

  // If a raw/base64 secret is provided, try base64 decode, else use as-is.
  $decoded = base64_decode($secret, true);
  return ($decoded !== false) ? $decoded : $secret;
}

function parse_svix_v1_signatures(string $sigHeader): array {
  $sigs = [];

  // Formats seen:
  // "v1,BASE64"
  // "t=TIMESTAMP,v1=BASE64"
  // "v1=BASE64"
  // Multiple signatures separated by spaces: "v1,AAA v1,BBB"
  foreach (preg_split('/\s+/', trim($sigHeader)) as $segment) {
    if ($segment === '') continue;

    $parts = array_map('trim', explode(',', $segment));

    // Case: ["v1", "BASE64"]
    if (count($parts) === 2 && strtolower($parts[0]) === 'v1' && $parts[1] !== '') {
      $sigs[] = $parts[1];
      continue;
    }

    foreach ($parts as $p) {
      if ($p === '') continue;

      if (str_starts_with($p, 'v1=')) {
        $val = substr($p, 3);
        if ($val !== '') $sigs[] = $val;
        continue;
      }

      if (str_starts_with($p, 'v1,')) {
        $val = substr($p, 3);
        if ($val !== '') $sigs[] = $val;
        continue;
      }
    }
  }

  return array_values(array_unique($sigs));
}

function verify_signature(array $h, string $raw, string $secret): bool {
  $sigHeader = (string)($h['webhook-signature'] ?? $h['svix-signature'] ?? '');
  $id = (string)($h['webhook-id'] ?? $h['svix-id'] ?? '');
  $ts = (string)($h['webhook-timestamp'] ?? $h['svix-timestamp'] ?? '');

  if ($sigHeader === '' || $id === '' || $ts === '') return false;

  // Anti-replay (5 minutes). If timestamp isn't numeric, skip window check.
  if (ctype_digit($ts)) {
    $delta = abs(time() - (int)$ts);
    if ($delta > 300) return false;
  }

  $sigs = parse_svix_v1_signatures($sigHeader);
  if (!$sigs) return false;

  $secretKey = svix_secret_bytes($secret);
  $signed = $id . '.' . $ts . '.' . $raw;
  $expected = base64_encode(hash_hmac('sha256', $signed, $secretKey, true));

  foreach ($sigs as $sig) {
    if (hash_equals($sig, $expected)) return true;
  }

  return false;
}

function yoco_get_checkout(string $checkoutId, array $cfg): ?array {
  $base = rtrim((string)($cfg['yoco_api_base'] ?? 'https://payments.yoco.com/api'), '/');
  $key  = (string)($cfg['yoco_secret_key'] ?? '');
  if ($key === '') return null;

  $ch = curl_init($base . '/checkouts/' . rawurlencode($checkoutId));
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer {$key}"],
    CURLOPT_TIMEOUT => 20,
  ]);
  $res  = curl_exec($ch);
  $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  $d = json_decode((string)$res, true);
  if ($code >= 200 && $code < 300 && is_array($d)) return $d;

  logit('checkout_lookup_failed checkoutId=' . $checkoutId . ' http=' . $code . ' body=' . substr((string)$res, 0, 160));
  return null;
}

$raw = file_get_contents('php://input') ?: '';
if ($raw === '') {
  http_response_code(400);
  echo json_encode(['ok' => false, 'error' => 'Empty body']);
  exit;
}

$secret = (string)($cfg['yoco_webhook_secret'] ?? '');
if ($secret === '') {
  logit('MISSING yoco_webhook_secret in config.php');
  http_response_code(500);
  echo json_encode(['ok' => false, 'error' => 'Server misconfigured']);
  exit;
}

$h = headers_lower();

if (!verify_signature($h, $raw, $secret)) {
  logit('SIG_FAIL headers=' . implode(',', array_keys($h)));
  http_response_code(401);
  echo json_encode(['ok' => false, 'error' => 'Invalid signature']);
  exit;
}

$event = json_decode($raw, true);
if (!is_array($event)) {
  http_response_code(400);
  echo json_encode(['ok' => false, 'error' => 'Invalid JSON']);
  exit;
}

$eventId   = (string)($event['id'] ?? '');
$eventType = (string)($event['type'] ?? '');
$payload   = $event['payload'] ?? [];
$plMeta    = is_array($payload) ? ($payload['metadata'] ?? []) : [];
$status    = strtolower((string)($payload['status'] ?? ''));

$checkoutId = (string)($plMeta['checkoutId'] ?? $plMeta['checkout_id'] ?? '');
if ($checkoutId === '') {
  if (preg_match('/\bch_[A-Za-z0-9]+\b/', $raw, $m)) $checkoutId = $m[0];
}

logit('VERIFIED eventId=' . $eventId . ' type=' . $eventType . ' status=' . $status . ' checkoutId=' . $checkoutId);

if ($checkoutId === '') {
  http_response_code(200);
  echo json_encode(['ok' => true, 'ignored' => true, 'reason' => 'missing_checkoutId']);
  exit;
}

// Do not trust webhook type/status alone. Always confirm checkout status via API.
$checkout = yoco_get_checkout($checkoutId, $cfg);
if (!$checkout) {
  http_response_code(200);
  echo json_encode(['ok' => true, 'ignored' => true, 'reason' => 'checkout_lookup_failed']);
  exit;
}

$checkoutStatus = strtolower((string)($checkout['status'] ?? ''));
if ($checkoutStatus !== 'completed') {
  http_response_code(200);
  echo json_encode(['ok' => true, 'ignored' => true, 'reason' => 'checkout_not_completed', 'status' => $checkoutStatus]);
  exit;
}

$meta = $checkout['metadata'] ?? [];
$tenantRef = (string)($meta['tenant_ref'] ?? '');
$email     = (string)($meta['email'] ?? '');

if (!preg_match('/^t_[a-f0-9]{16}$/', $tenantRef)) {
  logit('INVALID_TENANT_REF checkoutId=' . $checkoutId . ' tenant_ref=' . $tenantRef);
  http_response_code(200);
  echo json_encode(['ok' => true, 'ignored' => true, 'reason' => 'invalid_tenant_ref']);
  exit;
}

$tenantDir   = $tenantsDir . '/' . $tenantRef;
$envPath     = $tenantDir . '/.env';
$activePath  = $tenantDir . '/active.json';
$pendingPath = $tenantDir . '/pending.json';

if (!ensure_dir($tenantDir, 0750) || !ensure_dir($tenantDir . '/data', 0750)) {
  logit('MKDIR_FAIL tenant=' . $tenantRef);
  http_response_code(500);
  echo json_encode(['ok' => false, 'error' => 'Failed to create tenant folder']);
  exit;
}

$license = env_get($envPath, 'LICENSE_KEY');
if ($license === '') $license = 'lic_' . bin2hex(random_bytes(16));

env_set_if_missing($envPath, [
  'TENANT_REF'      => $tenantRef,
  'CHECKOUT_ID'     => $checkoutId,
  'EMAIL'           => $email,
  'LICENSE_KEY'     => $license,
  'WP_URL'          => '',
  'WP_USER'         => '',
  'WP_APP_PASSWORD' => '',
  'OPENAI_API_KEY'  => '',
], 0640);

$active = read_json($activePath) ?: [];
if (!is_array($active)) $active = [];

$download = is_array($active['download'] ?? null) ? $active['download'] : [];
$token = (string)($download['token'] ?? '');
if ($token === '') {
  $token = bin2hex(random_bytes(16));
}

$activeOut = array_merge($active, [
  'tenant_ref'   => $tenantRef,
  'checkoutId'   => $checkoutId,
  'email'        => $email,
  'status'       => 'active',
  'activated_at' => $active['activated_at'] ?? date('c'),
  'download'     => array_merge([
    'token' => $token,
    'token_issued_at' => $download['token_issued_at'] ?? date('c'),
    'last_token' => $download['last_token'] ?? null,
    'last_used_at' => $download['last_used_at'] ?? null,
    'last_used_ip' => $download['last_used_ip'] ?? null,
    'last_used_ua' => $download['last_used_ua'] ?? null,
  ], $download),
]);

if (!write_json_atomic($activePath, $activeOut, 0640)) {
  logit('ACTIVE_WRITE_FAIL tenant=' . $tenantRef);
  http_response_code(500);
  echo json_encode(['ok' => false, 'error' => 'Failed to write active.json']);
  exit;
}

$pending = read_json($pendingPath);
if (is_array($pending)) {
  $pending['status'] = 'active';
  $pending['activated_at'] = $pending['activated_at'] ?? date('c');
  @write_json_atomic($pendingPath, $pending, 0640);
}

logit('PROVISIONED tenant=' . $tenantRef . ' active=1 env=1');

http_response_code(200);
echo json_encode(['ok' => true, 'tenant_ref' => $tenantRef]);
