<?php
declare(strict_types=1);

require_once __DIR__ . '/../includes/bootstrap.php';
require_once __DIR__ . '/../includes/StripeClient.php';
require_once __DIR__ . '/../includes/FreeTier.php';

// Webhook must accept POST only, must verify signature, must be idempotent.
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    http_response_code(405);
    echo 'Method not allowed';
    exit;
}

$payload = file_get_contents('php://input');
$sig = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
$secret = StripeClient::config('STRIPE_WEBHOOK_SECRET');

if ($payload === false || $payload === '' || $sig === '' || $secret === '') {
    http_response_code(400);
    error_log('[stripe-webhook] missing payload, signature or secret');
    echo 'Bad request';
    exit;
}

if (!StripeClient::verifyWebhookSignature($payload, $sig, $secret)) {
    http_response_code(400);
    error_log('[stripe-webhook] signature verification failed');
    echo 'Invalid signature';
    exit;
}

$event = json_decode($payload, true);
if (!is_array($event)) {
    http_response_code(400);
    echo 'Invalid JSON';
    exit;
}

$eventId = (string)($event['id'] ?? '');
$type    = (string)($event['type'] ?? '');
$object  = (array)($event['data']['object'] ?? []);

if ($eventId === '' || $type === '') {
    http_response_code(400);
    echo 'Missing event metadata';
    exit;
}

$db = dbnmDb();

// Idempotency: skip if we've already processed this event_id.
$check = $db->prepare('SELECT 1 FROM stripe_events WHERE stripe_event_id = ? LIMIT 1');
$check->execute([$eventId]);
if ($check->fetchColumn()) {
    http_response_code(200);
    echo 'Already processed';
    exit;
}

try {
    $db->prepare('INSERT INTO stripe_events (stripe_event_id, type, payload, processed_at) VALUES (?, ?, ?, NOW())')
       ->execute([$eventId, $type, json_encode($event, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]);

    switch ($type) {
        case 'checkout.session.completed':
            handleCheckoutCompleted($db, $object);
            break;

        case 'customer.subscription.created':
        case 'customer.subscription.updated':
            handleSubscriptionChange($db, $object);
            break;

        case 'customer.subscription.deleted':
            handleSubscriptionDeleted($db, $object);
            break;

        case 'invoice.paid':
            handleInvoicePaid($db, $object);
            break;

        case 'invoice.payment_failed':
            handleInvoiceFailed($db, $object);
            break;

        default:
            // Unhandled event types are fine — Stripe just needs a 2xx response.
            break;
    }
    http_response_code(200);
    echo 'OK';
} catch (Throwable $e) {
    error_log('[stripe-webhook] handler error: ' . $e->getMessage());
    // Return 500 so Stripe retries — but the stripe_events row already exists, so a retry will be idempotent-skipped.
    // To re-process: delete the row before retry.
    http_response_code(500);
    echo 'Handler failure';
}

// ── Handlers ────────────────────────────────────────────────────────────────

function handleCheckoutCompleted(PDO $db, array $session): void
{
    $mode = (string)($session['mode'] ?? '');
    $metadata = (array)($session['metadata'] ?? []);
    $userId = (int)($metadata['user_id'] ?? 0);
    if ($userId <= 0) {
        error_log('[stripe-webhook] checkout.session.completed missing user_id metadata');
        return;
    }
    // Store Stripe customer ID for portal access later.
    $customerId = (string)($session['customer'] ?? '');
    if ($customerId !== '') {
        $db->prepare('UPDATE user_tool_credits SET stripe_customer_id = ? WHERE user_id = ? AND (stripe_customer_id IS NULL OR stripe_customer_id = "")')
           ->execute([$customerId, $userId]);
    }

    if ($mode === 'payment') {
        // One-time topup — grant credits immediately.
        $sku = (string)($metadata['sku'] ?? '');
        $credits = StripeClient::topupCredits($sku);
        if ($credits > 0) {
            FreeTier::awardBonus($userId, $credits, 'topup:' . $sku);
        }
    }
    // For mode='subscription', subscription.created will fire and update the tier.
}

function handleSubscriptionChange(PDO $db, array $sub): void
{
    $subId = (string)($sub['id'] ?? '');
    $status = (string)($sub['status'] ?? '');
    $customerId = (string)($sub['customer'] ?? '');
    $items = (array)($sub['items']['data'] ?? []);
    $priceId = (string)($items[0]['price']['id'] ?? '');
    $tier = StripeClient::tierForPrice($priceId);
    if ($tier === null) {
        error_log('[stripe-webhook] unknown price_id on subscription: ' . $priceId);
        return;
    }
    $metadata = (array)($sub['metadata'] ?? []);
    $userId = (int)($metadata['user_id'] ?? 0);
    if ($userId <= 0) {
        // Fallback: look up by stripe_customer_id
        $stmt = $db->prepare('SELECT user_id FROM user_tool_credits WHERE stripe_customer_id = ? LIMIT 1');
        $stmt->execute([$customerId]);
        $userId = (int)($stmt->fetchColumn() ?: 0);
    }
    if ($userId <= 0) {
        error_log('[stripe-webhook] subscription.* could not resolve user_id (sub=' . $subId . ', customer=' . $customerId . ')');
        return;
    }

    $periodEndTs = (int)($sub['current_period_end'] ?? 0);
    $periodStartTs = (int)($sub['current_period_start'] ?? 0);
    $periodEndIso = $periodEndTs > 0 ? gmdate('Y-m-d H:i:s', $periodEndTs) : null;
    $periodStartIso = $periodStartTs > 0 ? gmdate('Y-m-d H:i:s', $periodStartTs) : null;

    // Upsert subscription ledger
    $db->prepare(
        'INSERT INTO user_subscriptions
           (user_id, stripe_customer_id, stripe_subscription_id, tier, status, current_period_start, current_period_end)
         VALUES (?, ?, ?, ?, ?, ?, ?)
         ON DUPLICATE KEY UPDATE
           status = VALUES(status),
           tier = VALUES(tier),
           current_period_start = VALUES(current_period_start),
           current_period_end = VALUES(current_period_end),
           stripe_customer_id = VALUES(stripe_customer_id)'
    )->execute([$userId, $customerId, $subId, $tier, $status, $periodStartIso, $periodEndIso]);

    // Only flip the live tier flag if subscription is active/trialing.
    if (in_array($status, ['active', 'trialing'], true)) {
        FreeTier::setTier($userId, $tier, $customerId, $subId, $periodEndIso);
    } elseif (in_array($status, ['canceled', 'unpaid', 'incomplete_expired'], true)) {
        FreeTier::clearTier($userId);
    }
}

function handleSubscriptionDeleted(PDO $db, array $sub): void
{
    $subId = (string)($sub['id'] ?? '');
    $customerId = (string)($sub['customer'] ?? '');
    $stmt = $db->prepare('SELECT user_id FROM user_subscriptions WHERE stripe_subscription_id = ? LIMIT 1');
    $stmt->execute([$subId]);
    $userId = (int)($stmt->fetchColumn() ?: 0);
    if ($userId <= 0 && $customerId !== '') {
        $stmt = $db->prepare('SELECT user_id FROM user_tool_credits WHERE stripe_customer_id = ? LIMIT 1');
        $stmt->execute([$customerId]);
        $userId = (int)($stmt->fetchColumn() ?: 0);
    }
    if ($userId > 0) {
        $db->prepare('UPDATE user_subscriptions SET status = ? WHERE stripe_subscription_id = ?')
           ->execute(['canceled', $subId]);
        FreeTier::clearTier($userId);
    }
}

function handleInvoicePaid(PDO $db, array $invoice): void
{
    $subId = (string)($invoice['subscription'] ?? '');
    if ($subId === '') return;
    $stmt = $db->prepare('SELECT user_id, tier FROM user_subscriptions WHERE stripe_subscription_id = ? LIMIT 1');
    $stmt->execute([$subId]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$row) return;

    $userId = (int)$row['user_id'];
    $tier = (string)$row['tier'];
    $periodEndTs = (int)($invoice['lines']['data'][0]['period']['end'] ?? 0);
    $periodEndIso = $periodEndTs > 0 ? gmdate('Y-m-d H:i:s', $periodEndTs) : null;

    FreeTier::refillForRenewal($userId, $tier, $periodEndIso);
}

function handleInvoiceFailed(PDO $db, array $invoice): void
{
    $subId = (string)($invoice['subscription'] ?? '');
    if ($subId === '') return;
    $db->prepare('UPDATE user_subscriptions SET status = ? WHERE stripe_subscription_id = ?')
       ->execute(['past_due', $subId]);
    // Don't downgrade yet — Stripe will retry. subscription.updated will fire if it fully fails.
}
