<?php
// ─────────────────────────────────────────────────────────────
//  api/booking.php — All booking API routes (public + admin)
// ─────────────────────────────────────────────────────────────
require_once dirname(__DIR__) . '/lib.php';
require_once dirname(__DIR__) . '/lib/BookingEngine.php';
require_once dirname(__DIR__) . '/lib/ModuleToggle.php';
if (class_exists('\Stripe\Stripe') || file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
    if (file_exists(dirname(__DIR__) . '/lib/StripeBooking.php'))
        require_once dirname(__DIR__) . '/lib/StripeBooking.php';
}

cors();
session_start_safe();
init_booking_db();

$method = $_SERVER['REQUEST_METHOD'];

if (!empty($GLOBALS['BOOKING_ROUTE_SEGS']) && is_array($GLOBALS['BOOKING_ROUTE_SEGS'])) {
    // Routed via api/index.php with normalized segments.
    $segs = array_values(array_filter($GLOBALS['BOOKING_ROUTE_SEGS'], 'strlen'));
} else {
    $uri  = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
    $base = rtrim(dirname(dirname($_SERVER['SCRIPT_NAME'])), '/');
    $path = '/' . trim(substr($uri, strlen($base)), '/');
    $segs = array_values(array_filter(explode('/', $path)));

    // Direct hit or fallback include: trim /api/booking prefix when present.
    if (isset($segs[0]) && $segs[0] === 'api') array_shift($segs);
    if (isset($segs[0]) && $segs[0] === 'booking') array_shift($segs);
}

$res = $segs[0] ?? '';
$id  = isset($segs[1]) ? $segs[1] : null;
$sub = isset($segs[2]) ? $segs[2] : null;

// ── Stripe webhook (must read raw body before any output) ────
if ($method === 'POST' && $res === 'stripe' && $id === 'webhook') {
    $payload   = file_get_contents('php://input');
    $sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
    try {
        StripeBooking::handleWebhook($payload, $sigHeader);
        json_out(['ok' => true]);
    } catch (\Throwable $e) {
        error_log('Stripe webhook error: ' . $e->getMessage());
        json_out(['ok' => false, 'error' => $e->getMessage()], 400);
    }
    exit;
}

// ── Parse JSON body for non-webhook POSTs/PUTs ───────────────
$body = [];
if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) {
    $raw = file_get_contents('php://input');
    if ($raw) $body = json_decode($raw, true) ?? [];
    // Also check $_POST
    if (empty($body) && !empty($_POST)) $body = $_POST;
}

try {

// ════════════════════════════════════════════════════════════
//  PUBLIC ROUTES
// ════════════════════════════════════════════════════════════

// GET /api/booking/config — returns Stripe publishable key + currency
if ($method === 'GET' && $res === 'config' && $id === null) {
    if (class_exists('StripeBooking')) {
        json_out(array_merge(['ok' => true], StripeBooking::publicConfig()));
    }
    json_out([
        'ok'          => true,
        'mode'        => defined('STRIPE_MODE') ? STRIPE_MODE : 'test',
        'stripe_key'  => defined('STRIPE_PUBLISHABLE_KEY') ? STRIPE_PUBLISHABLE_KEY : '',
        'currency'    => defined('STRIPE_CURRENCY') ? STRIPE_CURRENCY : 'usd',
        'stripe_ready'=> defined('STRIPE_SECRET_KEY') && STRIPE_SECRET_KEY !== '',
    ]);
}

// GET /api/booking/plans
if ($method === 'GET' && $res === 'plans' && $id === null) {
    $plans = db()->query(
        'SELECT * FROM bk_plans WHERE is_active=1 ORDER BY price ASC'
    )->fetchAll();

    foreach ($plans as &$p) {
        $svcs = db()->prepare(
            'SELECT s.id, s.name, s.duration_min
             FROM bk_plan_services ps
             JOIN bk_services s ON s.id = ps.service_id
             WHERE ps.plan_id=? AND s.is_active=1'
        );
        $svcs->execute([$p['id']]);
        $p['services'] = $svcs->fetchAll();
        $p['price'] = (float)$p['price'];
    }
    json_out(['ok' => true, 'plans' => $plans]);
}

// GET /api/booking/services
if ($method === 'GET' && $res === 'services' && $id === null) {
    $svcs = db()->query(
        'SELECT * FROM bk_services WHERE is_active=1 ORDER BY name'
    )->fetchAll();
    json_out(['ok' => true, 'services' => $svcs]);
}

// GET /api/booking/providers?service_id=X
if ($method === 'GET' && $res === 'providers' && $id === null) {
    $serviceId = (int)($_GET['service_id'] ?? 0);
    if ($serviceId) {
        $rows = db()->prepare(
            'SELECT p.id, p.name, p.branch_name, p.bio, p.avatar_url,
                    ps.price, ps.capacity_per_slot
             FROM bk_providers p
             JOIN bk_provider_services ps ON ps.provider_id = p.id
             WHERE ps.service_id=? AND p.is_active=1
             ORDER BY p.name'
        );
        $rows->execute([$serviceId]);
    } else {
        $rows = db()->query('SELECT * FROM bk_providers WHERE is_active=1 ORDER BY name');
    }
    $providers = $rows->fetchAll();
    foreach ($providers as &$pr) {
        if (isset($pr['price'])) $pr['price'] = $pr['price'] !== null ? (float)$pr['price'] : null;
    }
    json_out(['ok' => true, 'providers' => $providers]);
}

// GET /api/booking/slots?service_id=X&provider_id=Y&date=YYYY-MM-DD
if ($method === 'GET' && $res === 'slots') {
    $serviceId  = (int)($_GET['service_id'] ?? 0);
    $providerId = (int)($_GET['provider_id'] ?? 0);
    $date       = preg_replace('/[^0-9\-]/', '', $_GET['date'] ?? '');

    if (!$serviceId || !$providerId || !$date) {
        json_out(['ok' => false, 'error' => 'Missing service_id, provider_id, or date'], 400);
    }

    $slots = BookingEngine::getSlots($providerId, $serviceId, $date);
    json_out(['ok' => true, 'slots' => $slots]);
}

// ── POST /api/booking/checkin — QR check-in (admin only) ─────
if ($method === 'POST' && $res === 'checkin') {
    require_once __DIR__ . '/booking/checkin.php';
    exit;
}

// POST /api/booking/check-membership
if ($method === 'POST' && $res === 'check-membership') {
    $email = trim(strtolower($body['email'] ?? ''));
    if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        json_out(['ok' => false, 'error' => 'Valid email required'], 400);
    }

    $cust = db()->prepare('SELECT * FROM bk_customers WHERE email=?');
    $cust->execute([$email]);
    $customer = $cust->fetch();

    if (!$customer) {
        json_out(['ok' => true, 'found' => false]);
    }

    $mem = db()->prepare(
        'SELECT m.*, p.name as plan_name FROM bk_memberships m
         JOIN bk_plans p ON p.id = m.plan_id
         WHERE m.customer_id=? AND m.status="active" AND m.expires_at > NOW()
         ORDER BY m.expires_at DESC LIMIT 1'
    );
    $mem->execute([$customer['id']]);
    $membership = $mem->fetch();

    if (!$membership) {
        json_out(['ok' => true, 'found' => false, 'customer_exists' => true]);
    }

    $services = BookingEngine::getUnlockedServices($email);

    json_out([
        'ok'               => true,
        'found'            => true,
        'customer'         => ['id' => $customer['id'], 'email' => $customer['email'], 'name' => $customer['name'], 'phone' => $customer['phone']],
        'membership'       => [
            'id'                 => $membership['id'],
            'plan_name'          => $membership['plan_name'],
            'expires_at'         => $membership['expires_at'],
            'status'             => $membership['status'],
            'sessions_remaining' => $membership['sessions_remaining'], // null = unlimited
        ],
        'unlocked_services'=> $services,
    ]);
}

// POST /api/booking/appointments
if ($method === 'POST' && $res === 'appointments') {
    $email      = trim(strtolower($body['email'] ?? ''));
    $name       = trim($body['name'] ?? '');
    $phone      = trim($body['phone'] ?? '');
    $serviceId  = (int)($body['service_id'] ?? 0);
    $providerId = (int)($body['provider_id'] ?? 0);
    $datetime   = $body['datetime'] ?? '';
    $notes      = trim($body['notes'] ?? '');

    if (!$email || !$name || !$serviceId || !$providerId || !$datetime) {
        json_out(['ok' => false, 'error' => 'Missing required fields'], 400);
    }
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        json_out(['ok' => false, 'error' => 'Invalid email'], 400);
    }

    // Validate datetime format
    $dtObj = DateTime::createFromFormat('Y-m-d H:i:s', $datetime);
    if (!$dtObj) $dtObj = DateTime::createFromFormat('Y-m-d H:i', $datetime);
    if (!$dtObj) json_out(['ok' => false, 'error' => 'Invalid datetime format'], 400);
    $datetime = $dtObj->format('Y-m-d H:i:s');

    // Get service — check restricted flag first
    $svcRow = db()->prepare('SELECT duration_min, is_restricted FROM bk_services WHERE id=? AND is_active=1');
    $svcRow->execute([$serviceId]);
    $svc = $svcRow->fetch();
    if (!$svc) json_out(['ok' => false, 'error' => 'Service not found'], 400);

    // Walk-in path: unrestricted services skip membership check
    $access = ['allowed' => false, 'membership' => null];
    if ((int)$svc['is_restricted'] === 0) {
        // Walk-in allowed — no membership required
        $access = ['allowed' => true, 'membership' => null];
    } else {
        $access = BookingEngine::checkAccess($email, $serviceId);
        if (!$access['allowed']) {
            json_out(['ok' => false, 'error' => 'No active membership for this service', 'reason' => $access['reason']], 403);
        }
    }

    $endDatetime = date('Y-m-d H:i:s', strtotime($datetime) + $svc['duration_min'] * 60);

    // Wrap in transaction to prevent race conditions
    db()->beginTransaction();
    try {
        // Re-check availability inside transaction
        if (!BookingEngine::isSlotAvailable($providerId, $serviceId, $datetime)) {
            db()->rollBack();
            json_out(['ok' => false, 'error' => 'This slot is no longer available'], 409);
        }

        // Get or create customer
        $custRow = db()->prepare('SELECT id FROM bk_customers WHERE email=?');
        $custRow->execute([$email]);
        $cust = $custRow->fetch();
        if (!$cust) {
            db()->prepare('INSERT INTO bk_customers (email, name, phone) VALUES (?, ?, ?)')
                ->execute([$email, $name, $phone]);
            $customerId = (int) db()->lastInsertId();
        } else {
            $customerId = (int) $cust['id'];
            // Update name/phone if provided
            if ($name || $phone) {
                db()->prepare('UPDATE bk_customers SET name=COALESCE(NULLIF(?,\'\'),name), phone=COALESCE(NULLIF(?,\'\'),phone) WHERE id=?')
                    ->execute([$name, $phone, $customerId]);
            }
        }

        $ref = BookingEngine::genRef();
        $membershipId = $access['membership'] ? (int)$access['membership']['id'] : null;

        // ── Phase 1: session limit check ──────────────────────
        if ($membershipId) {
            $sessionCheck = BookingEngine::checkSessionLimit($membershipId);
            if (!$sessionCheck['ok']) {
                db()->rollBack();
                json_out(['ok' => false, 'error' => $sessionCheck['error']], 403);
            }
        }

        db()->prepare(
            'INSERT INTO bk_appointments
             (ref, customer_id, provider_id, service_id, membership_id,
              start_datetime, end_datetime, notes, status)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, "pending")'
        )->execute([$ref, $customerId, $providerId, $serviceId, $membershipId,
                    $datetime, $endDatetime, $notes]);

        $apptId = (int) db()->lastInsertId();

        // ── Phase 1: deduct session atomically ────────────────
        if ($membershipId) {
            if (!BookingEngine::deductSession($membershipId)) {
                db()->rollBack();
                json_out(['ok' => false, 'error' => 'No sessions remaining. Please renew your membership.'], 403);
            }
        }

        db()->commit();

    } catch (\Throwable $e) {
        db()->rollBack();
        throw $e;
    }

    // Load full appointment for response + email
    $apptRow = db()->prepare(
        'SELECT a.*, c.name as customer_name, c.email as customer_email,
                s.name as service_name, p.name as provider_name, p.email as provider_email
         FROM bk_appointments a
         JOIN bk_customers c ON c.id = a.customer_id
         JOIN bk_services s ON s.id = a.service_id
         JOIN bk_providers p ON p.id = a.provider_id
         WHERE a.id=?'
    );
    $apptRow->execute([$apptId]);
    $appt = $apptRow->fetch();

    // Send confirmation emails
    $bookUrl = APP_URL . '/booking/book.php';
    $apptDate = date('D, d M Y \a\t g:ia', strtotime($datetime));
    $htmlCust = "
        <h2>Booking Confirmed — {$appt['service_name']}</h2>
        <p>Hi {$name},</p>
        <p>Your appointment is confirmed.</p>
        <table>
            <tr><td><strong>Reference:</strong></td><td>{$ref}</td></tr>
            <tr><td><strong>Service:</strong></td><td>{$appt['service_name']}</td></tr>
            <tr><td><strong>Provider:</strong></td><td>{$appt['provider_name']}</td></tr>
            <tr><td><strong>Date &amp; Time:</strong></td><td>{$apptDate}</td></tr>
        </table>
        <p><a href=\"{$bookUrl}\">Book another appointment →</a></p>";
    $textCust = "Booking Confirmed — {$ref}\nService: {$appt['service_name']}\nProvider: {$appt['provider_name']}\nDate: {$apptDate}";

    try {
        send_mail($email, $name, "Booking Confirmed — {$appt['service_name']} with {$appt['provider_name']}", $htmlCust, $textCust);
        db()->prepare('UPDATE bk_appointments SET email_sent=1 WHERE id=?')->execute([$apptId]);
    } catch (\Throwable $e) {
        error_log('Booking confirmation email error: ' . $e->getMessage());
    }

    // Notify provider
    if ($appt['provider_email']) {
        $htmlProv = "
            <h2>New Booking — {$appt['customer_name']}</h2>
            <p>A new appointment has been booked with you.</p>
            <table>
                <tr><td><strong>Reference:</strong></td><td>{$ref}</td></tr>
                <tr><td><strong>Customer:</strong></td><td>{$appt['customer_name']} ({$email})</td></tr>
                <tr><td><strong>Service:</strong></td><td>{$appt['service_name']}</td></tr>
                <tr><td><strong>Date &amp; Time:</strong></td><td>{$apptDate}</td></tr>
            </table>";
        $textProv = "New Booking — {$ref}\nCustomer: {$appt['customer_name']}\nService: {$appt['service_name']}\nDate: {$apptDate}";
        try {
            send_mail($appt['provider_email'], $appt['provider_name'], "New Booking — {$appt['customer_name']} for {$appt['service_name']}", $htmlProv, $textProv);
        } catch (\Throwable $e) {
            error_log('Provider notification email error: ' . $e->getMessage());
        }
    }

    json_out(['ok' => true, 'ref' => $ref, 'appointment' => $appt]);
}

// POST /api/booking/waitlist
if ($method === 'POST' && $res === 'waitlist' && $id === null) {
    $email      = trim(strtolower($body['email'] ?? ''));
    $name       = trim($body['name'] ?? '');
    $serviceId  = (int)($body['service_id'] ?? 0);
    $providerId = (int)($body['provider_id'] ?? 0);
    $slotDt     = $body['slot_datetime'] ?? '';

    if (!$email || !$name || !$serviceId || !$providerId || !$slotDt) {
        json_out(['ok' => false, 'error' => 'Missing required fields'], 400);
    }

    db()->prepare(
        'INSERT INTO bk_waitlist (customer_email, customer_name, provider_id, service_id, slot_datetime)
         VALUES (?, ?, ?, ?, ?)'
    )->execute([$email, $name, $providerId, $serviceId, $slotDt]);

    json_out(['ok' => true]);
}

// POST /api/booking/validate-promo
if ($method === 'POST' && $res === 'validate-promo') {
    $code   = strtoupper(trim($body['code'] ?? ''));
    $planId = (int)($body['plan_id'] ?? 0);
    if (!$code) json_out(['ok' => false, 'error' => 'Code required'], 400);

    $stmt = db()->prepare(
        'SELECT * FROM bk_promo_codes
         WHERE code=? AND is_active=1
           AND (valid_from IS NULL OR valid_from <= NOW())
           AND (valid_until IS NULL OR valid_until >= NOW())
           AND (max_uses IS NULL OR uses_count < max_uses)
         LIMIT 1'
    );
    $stmt->execute([$code]);
    $promo = $stmt->fetch();

    if (!$promo) json_out(['ok' => false, 'error' => 'Invalid or expired promo code'], 404);

    // Check plan restriction
    if ($promo['plan_ids'] && $planId) {
        $allowed = json_decode($promo['plan_ids'], true) ?: [];
        if (!empty($allowed) && !in_array($planId, $allowed)) {
            json_out(['ok' => false, 'error' => 'This code is not valid for the selected plan'], 400);
        }
    }

    json_out([
        'ok'             => true,
        'code'           => $promo['code'],
        'discount_type'  => $promo['discount_type'],
        'discount_value' => (float)$promo['discount_value'],
        'description'    => $promo['description'],
    ]);
}

// POST /api/booking/rate — submit a rating after appointment
if ($method === 'POST' && $res === 'rate') {
    $ref     = trim($body['ref'] ?? '');
    $rating  = (int)($body['rating'] ?? 0);
    $comment = trim($body['comment'] ?? '');
    $email   = trim(strtolower($body['email'] ?? ''));

    if (!$ref || !$rating || !$email) json_out(['ok' => false, 'error' => 'Missing required fields'], 400);
    if ($rating < 1 || $rating > 5) json_out(['ok' => false, 'error' => 'Rating must be 1-5'], 400);

    // Get appointment + verify customer owns it
    $apptRow = db()->prepare(
        'SELECT a.*, c.email as customer_email FROM bk_appointments a
         JOIN bk_customers c ON c.id = a.customer_id
         WHERE a.ref=?'
    );
    $apptRow->execute([$ref]);
    $appt = $apptRow->fetch();
    if (!$appt) json_out(['ok' => false, 'error' => 'Appointment not found'], 404);
    if (strtolower($appt['customer_email']) !== $email) json_out(['ok' => false, 'error' => 'Email does not match booking'], 403);
    if (!in_array($appt['status'], ['completed', 'confirmed'])) {
        json_out(['ok' => false, 'error' => 'Can only rate completed appointments'], 400);
    }

    try {
        db()->prepare(
            'INSERT INTO bk_ratings (appointment_id, customer_id, provider_id, service_id, rating, comment)
             VALUES (?, ?, ?, ?, ?, ?)'
        )->execute([$appt['id'], $appt['customer_id'], $appt['provider_id'], $appt['service_id'], $rating, $comment]);
    } catch (\Throwable $e) {
        json_out(['ok' => false, 'error' => 'You have already rated this appointment'], 409);
    }

    json_out(['ok' => true]);
}

// GET /api/booking/provider-ratings?provider_id=X
if ($method === 'GET' && $res === 'provider-ratings') {
    $providerId = (int)($_GET['provider_id'] ?? 0);
    if (!$providerId) json_out(['ok' => false, 'error' => 'provider_id required'], 400);

    $stmt = db()->prepare(
        'SELECT r.rating, r.comment, r.created_at,
                c.name as customer_name,
                s.name as service_name
         FROM bk_ratings r
         JOIN bk_customers c ON c.id = r.customer_id
         JOIN bk_services s ON s.id = r.service_id
         WHERE r.provider_id=? AND r.is_public=1
         ORDER BY r.created_at DESC LIMIT 20'
    );
    $stmt->execute([$providerId]);
    $reviews = $stmt->fetchAll();

    $avg = db()->prepare('SELECT AVG(rating) as avg, COUNT(*) as cnt FROM bk_ratings WHERE provider_id=? AND is_public=1');
    $avg->execute([$providerId]);
    $stats = $avg->fetch();

    json_out([
        'ok'      => true,
        'reviews' => $reviews,
        'average' => round((float)($stats['avg'] ?? 0), 1),
        'count'   => (int)($stats['cnt'] ?? 0),
    ]);
}


// POST /api/booking/stripe/checkout  (promo-code-aware)
if ($method === 'POST' && $res === 'stripe' && $id === 'checkout') {
    $planId     = (int)($body['plan_id'] ?? 0);
    $email      = trim($body['email'] ?? '');
    $name       = trim($body['name'] ?? '');
    $promoCode  = strtoupper(trim($body['promo_code'] ?? ''));
    $successUrl = $body['success_url'] ?? APP_URL . '/booking/plans.php';
    $cancelUrl  = $body['cancel_url']  ?? APP_URL . '/booking/plans.php';

    if (!$planId || !$email || !$name) {
        json_out(['ok' => false, 'error' => 'Missing plan_id, email, or name'], 400);
    }
    if (!class_exists('StripeBooking')) {
        json_out(['ok' => false, 'error' => 'Stripe not configured'], 503);
    }

    $discountAmount = 0.0;
    $validPromo     = null;
    if ($promoCode) {
        $ps = db()->prepare(
            'SELECT * FROM bk_promo_codes
             WHERE code=? AND is_active=1
               AND (valid_from IS NULL OR valid_from <= NOW())
               AND (valid_until IS NULL OR valid_until >= NOW())
               AND (max_uses IS NULL OR uses_count < max_uses)
             LIMIT 1'
        );
        $ps->execute([$promoCode]);
        $validPromo = $ps->fetch();
        if ($validPromo) {
            $planRow = db()->prepare('SELECT price FROM bk_plans WHERE id=?');
            $planRow->execute([$planId]);
            $plan = $planRow->fetch();
            if ($plan) {
                $price = (float)$plan['price'];
                $discountAmount = $validPromo['discount_type'] === 'percent'
                    ? round($price * (float)$validPromo['discount_value'] / 100, 2)
                    : min((float)$validPromo['discount_value'], $price);
            }
        }
    }

    $checkoutUrl = StripeBooking::createCheckout($planId, $email, $name, $successUrl, $cancelUrl, $discountAmount);
    if ($validPromo) {
        db()->prepare('UPDATE bk_promo_codes SET uses_count=uses_count+1 WHERE id=?')->execute([$validPromo['id']]);
    }
    json_out(['ok' => true, 'checkout_url' => $checkoutUrl,
              'discount_applied' => $discountAmount > 0, 'discount_amount' => $discountAmount]);
}

// ════════════════════════════════════════════════════════════
//  ADMIN ROUTES — require admin auth
// ════════════════════════════════════════════════════════════

if ($res === 'admin') {
    api_require_admin();
    $adminRes = $segs[1] ?? '';
    // ── Stripe config ──────────────────────────────────────────
    if ($adminRes === 'stripe-config') {
        $cfgFile = dirname(__DIR__) . '/config.php';
        if ($method === 'GET') {
            json_out(['ok' => true, 'stripe' => [
                'mode' => defined('STRIPE_MODE') ? STRIPE_MODE : 'test',
                'currency' => defined('STRIPE_CURRENCY') ? STRIPE_CURRENCY : 'usd',
                'test_publishable_key' => defined('STRIPE_TEST_PUBLISHABLE_KEY') ? STRIPE_TEST_PUBLISHABLE_KEY : (defined('STRIPE_PUBLISHABLE_KEY') ? STRIPE_PUBLISHABLE_KEY : ''),
                'test_secret_key' => defined('STRIPE_TEST_SECRET_KEY') ? STRIPE_TEST_SECRET_KEY : (defined('STRIPE_SECRET_KEY') ? STRIPE_SECRET_KEY : ''),
                'test_webhook_secret' => defined('STRIPE_TEST_WEBHOOK_SECRET') ? STRIPE_TEST_WEBHOOK_SECRET : (defined('STRIPE_WEBHOOK_SECRET') ? STRIPE_WEBHOOK_SECRET : ''),
                'live_publishable_key' => defined('STRIPE_LIVE_PUBLISHABLE_KEY') ? STRIPE_LIVE_PUBLISHABLE_KEY : '',
                'live_secret_key' => defined('STRIPE_LIVE_SECRET_KEY') ? STRIPE_LIVE_SECRET_KEY : '',
                'live_webhook_secret' => defined('STRIPE_LIVE_WEBHOOK_SECRET') ? STRIPE_LIVE_WEBHOOK_SECRET : '',
            ]]);
        }
        if ($method === 'PUT') {
            $mode = strtolower((string)($body['mode'] ?? 'test')) === 'live' ? 'live' : 'test';
            $currency = strtolower(trim((string)($body['currency'] ?? 'usd')));
            if (!preg_match('/^[a-z]{3}$/', $currency)) $currency = 'usd';

            $map = [
                'STRIPE_MODE' => $mode,
                'STRIPE_CURRENCY' => $currency,
                'STRIPE_TEST_PUBLISHABLE_KEY' => (string)($body['test_publishable_key'] ?? ''),
                'STRIPE_TEST_SECRET_KEY' => (string)($body['test_secret_key'] ?? ''),
                'STRIPE_TEST_WEBHOOK_SECRET' => (string)($body['test_webhook_secret'] ?? ''),
                'STRIPE_LIVE_PUBLISHABLE_KEY' => (string)($body['live_publishable_key'] ?? ''),
                'STRIPE_LIVE_SECRET_KEY' => (string)($body['live_secret_key'] ?? ''),
                'STRIPE_LIVE_WEBHOOK_SECRET' => (string)($body['live_webhook_secret'] ?? ''),
                // keep legacy constants aligned for compatibility
                'STRIPE_PUBLISHABLE_KEY' => $mode === 'live' ? (string)($body['live_publishable_key'] ?? '') : (string)($body['test_publishable_key'] ?? ''),
                'STRIPE_SECRET_KEY' => $mode === 'live' ? (string)($body['live_secret_key'] ?? '') : (string)($body['test_secret_key'] ?? ''),
                'STRIPE_WEBHOOK_SECRET' => $mode === 'live' ? (string)($body['live_webhook_secret'] ?? '') : (string)($body['test_webhook_secret'] ?? ''),
            ];

            $src = file_get_contents($cfgFile);
            if ($src === false) json_out(['ok' => false, 'error' => 'Cannot read config.php'], 500);
            foreach ($map as $const => $val) {
                $safe = addslashes($val);
                $replacement = "define('{$const}', '{$safe}')";
                if (preg_match("/define\('{$const}',\s*[^;]+\)/", $src)) {
                    $src = preg_replace("/define\('{$const}',\s*[^;]+\)/", $replacement, $src);
                } else {
                    $src .= "\ndefine('{$const}', '{$safe}');";
                }
            }
            if (file_put_contents($cfgFile, $src) === false) {
                json_out(['ok' => false, 'error' => 'Cannot write config.php'], 500);
            }
            json_out(['ok' => true]);
        }
        json_out(['ok' => false, 'error' => 'Method not allowed'], 405);
    }

    $adminId  = isset($segs[2]) ? $segs[2] : null;
    $adminSub = isset($segs[3]) ? $segs[3] : null;

    // ── Stats ─────────────────────────────────────────────────
    if ($method === 'GET' && $adminRes === 'stats') {
        $today = date('Y-m-d');
        $weekStart = date('Y-m-d', strtotime('monday this week'));
        $totalAppts  = (int) db()->query('SELECT COUNT(*) FROM bk_appointments')->fetchColumn();
        $todayAppts  = (int) db()->prepare('SELECT COUNT(*) FROM bk_appointments WHERE DATE(start_datetime)=?')->execute([$today]) ? db()->prepare('SELECT COUNT(*) FROM bk_appointments WHERE DATE(start_datetime)=?')->execute([$today]) && 0 : 0;

        // Re-query properly
        $stToday = db()->prepare('SELECT COUNT(*) FROM bk_appointments WHERE DATE(start_datetime)=?');
        $stToday->execute([$today]);
        $todayCount = (int) $stToday->fetchColumn();

        $stWeek = db()->prepare('SELECT COUNT(*) FROM bk_appointments WHERE DATE(start_datetime) >= ?');
        $stWeek->execute([$weekStart]);
        $weekCount = (int) $stWeek->fetchColumn();

        $stPending = db()->prepare('SELECT COUNT(*) FROM bk_appointments WHERE status="pending" AND DATE(start_datetime)=?');
        $stPending->execute([$today]);
        $pendingToday = (int) $stPending->fetchColumn();

        $activeMem = (int) db()->query('SELECT COUNT(*) FROM bk_memberships WHERE status="active" AND expires_at > NOW()')->fetchColumn();
        $expiredMem = (int) db()->query('SELECT COUNT(*) FROM bk_memberships WHERE status="expired" OR expires_at <= NOW()')->fetchColumn();

        $topSvcs = db()->query(
            'SELECT s.name, COUNT(*) as cnt FROM bk_appointments a
             JOIN bk_services s ON s.id = a.service_id
             GROUP BY a.service_id ORDER BY cnt DESC LIMIT 5'
        )->fetchAll();

        json_out([
            'ok'                  => true,
            'total_appointments'  => $totalAppts,
            'today'               => $todayCount,
            'this_week'           => $weekCount,
            'pending_today'       => $pendingToday,
            'active_memberships'  => $activeMem,
            'expired_memberships' => $expiredMem,
            'top_services'        => $topSvcs,
            'total_plans'         => (int) db()->query('SELECT COUNT(*) FROM bk_plans WHERE is_active=1')->fetchColumn(),
            'total_services'      => (int) db()->query('SELECT COUNT(*) FROM bk_services WHERE is_active=1')->fetchColumn(),
            'total_providers'     => (int) db()->query('SELECT COUNT(*) FROM bk_providers WHERE is_active=1')->fetchColumn(),
        ]);
    }

    // ── Appointments ──────────────────────────────────────────
    if ($adminRes === 'appointments') {
        if ($method === 'GET' && $adminId === null) {
            $where = ['1=1'];
            $params = [];
            if (!empty($_GET['date'])) { $where[] = 'DATE(a.start_datetime)=?'; $params[] = $_GET['date']; }
            if (!empty($_GET['provider_id'])) { $where[] = 'a.provider_id=?'; $params[] = (int)$_GET['provider_id']; }
            if (!empty($_GET['status'])) { $where[] = 'a.status=?'; $params[] = $_GET['status']; }
            $page  = max(1, (int)($_GET['page'] ?? 1));
            $limit = 50;
            $offset = ($page - 1) * $limit;
            $whereStr = implode(' AND ', $where);

            $cntStmt = db()->prepare("SELECT COUNT(*) FROM bk_appointments a WHERE {$whereStr}");
            $cntStmt->execute($params);
            $total = (int) $cntStmt->fetchColumn();

            $stmt = db()->prepare(
                "SELECT a.*, c.name as customer_name, c.email as customer_email,
                        s.name as service_name, p.name as provider_name
                 FROM bk_appointments a
                 JOIN bk_customers c ON c.id = a.customer_id
                 JOIN bk_services s ON s.id = a.service_id
                 JOIN bk_providers p ON p.id = a.provider_id
                 WHERE {$whereStr}
                 ORDER BY a.start_datetime DESC LIMIT {$limit} OFFSET {$offset}"
            );
            $stmt->execute($params);
            $rows = $stmt->fetchAll();
            json_out(['ok' => true, 'appointments' => $rows, 'total' => $total, 'page' => $page, 'pages' => ceil($total/$limit)]);
        }

        if ($method === 'GET' && $adminId !== null) {
            $stmt = db()->prepare(
                'SELECT a.*, c.name as customer_name, c.email as customer_email,
                        c.phone as customer_phone,
                        s.name as service_name, s.duration_min,
                        p.name as provider_name
                 FROM bk_appointments a
                 JOIN bk_customers c ON c.id = a.customer_id
                 JOIN bk_services s ON s.id = a.service_id
                 JOIN bk_providers p ON p.id = a.provider_id
                 WHERE a.id=?'
            );
            $stmt->execute([(int)$adminId]);
            $row = $stmt->fetch();
            if (!$row) json_out(['ok' => false, 'error' => 'Not found'], 404);
            json_out(['ok' => true, 'appointment' => $row]);
        }

        if ($method === 'PUT' && $adminId !== null) {
            $status = $body['status'] ?? null;
            $notes  = $body['notes']  ?? null;
            $allowed = ['pending','confirmed','cancelled','no_show','completed'];
            if ($status && !in_array($status, $allowed)) {
                json_out(['ok' => false, 'error' => 'Invalid status'], 400);
            }
            $sets = []; $params = [];
            if ($status !== null) { $sets[] = 'status=?'; $params[] = $status; }
            if ($notes  !== null) { $sets[] = 'notes=?';  $params[] = $notes; }
            if (empty($sets)) json_out(['ok' => false, 'error' => 'Nothing to update'], 400);
            $params[] = (int)$adminId;
            db()->prepare('UPDATE bk_appointments SET ' . implode(',', $sets) . ' WHERE id=?')->execute($params);
            json_out(['ok' => true]);
        }

        if ($method === 'DELETE' && $adminId !== null) {
            db()->prepare('DELETE FROM bk_appointments WHERE id=?')->execute([(int)$adminId]);
            json_out(['ok' => true]);
        }
    }

    // ── Memberships ───────────────────────────────────────────
    if ($adminRes === 'memberships') {
        if ($method === 'GET' && $adminId === null) {
            $where = ['1=1']; $params = [];
            if (!empty($_GET['status'])) {
                if ($_GET['status'] === 'active') { $where[] = 'm.status="active" AND m.expires_at > NOW()'; }
                elseif ($_GET['status'] === 'expired') { $where[] = '(m.status="expired" OR m.expires_at <= NOW())'; }
                else { $where[] = 'm.status=?'; $params[] = $_GET['status']; }
            }
            if (!empty($_GET['search'])) {
                $like = '%' . $_GET['search'] . '%';
                $where[] = '(c.email LIKE ? OR c.name LIKE ?)';
                $params[] = $like; $params[] = $like;
            }
            $page = max(1, (int)($_GET['page'] ?? 1));
            $limit = 50; $offset = ($page - 1) * $limit;
            $whereStr = implode(' AND ', $where);

            $cntStmt = db()->prepare("SELECT COUNT(*) FROM bk_memberships m JOIN bk_customers c ON c.id=m.customer_id WHERE {$whereStr}");
            $cntStmt->execute($params);
            $total = (int) $cntStmt->fetchColumn();

            $stmt = db()->prepare(
                "SELECT m.*, c.name as customer_name, c.email as customer_email,
                        p.name as plan_name, p.session_limit
                 FROM bk_memberships m
                 JOIN bk_customers c ON c.id = m.customer_id
                 JOIN bk_plans p ON p.id = m.plan_id
                 WHERE {$whereStr}
                 ORDER BY m.created_at DESC LIMIT {$limit} OFFSET {$offset}"
            );
            $stmt->execute($params);
            json_out(['ok' => true, 'memberships' => $stmt->fetchAll(), 'total' => $total, 'page' => $page, 'pages' => ceil($total/$limit)]);
        }

        if ($method === 'POST') {
            $custEmail = trim(strtolower($body['customer_email'] ?? ''));
            $planId    = (int)($body['plan_id'] ?? 0);
            $startsAt  = $body['starts_at'] ?? date('Y-m-d H:i:s');
            $notes     = $body['notes'] ?? '';

            if (!$custEmail || !$planId) json_out(['ok' => false, 'error' => 'customer_email and plan_id required'], 400);

            $planRow = db()->prepare('SELECT * FROM bk_plans WHERE id=?');
            $planRow->execute([$planId]);
            $plan = $planRow->fetch();
            if (!$plan) json_out(['ok' => false, 'error' => 'Plan not found'], 404);

            $custRow = db()->prepare('SELECT id FROM bk_customers WHERE email=?');
            $custRow->execute([$custEmail]);
            $cust = $custRow->fetch();
            if (!$cust) {
                db()->prepare('INSERT INTO bk_customers (email, name) VALUES (?, ?)')->execute([$custEmail, $custEmail]);
                $customerId = (int) db()->lastInsertId();
            } else {
                $customerId = (int) $cust['id'];
            }

            $expiresAt = date('Y-m-d H:i:s', strtotime($startsAt . ' +' . $plan['duration_days'] . ' days'));
            $sessionLimit      = isset($plan['session_limit']) ? (int)$plan['session_limit'] : 0;
            $sessionsRemaining = $sessionLimit > 0 ? $sessionLimit : null;
            db()->prepare(
                'INSERT INTO bk_memberships
                 (customer_id, plan_id, starts_at, expires_at, status, sessions_remaining, activated_by, notes)
                 VALUES (?, ?, ?, ?, "active", ?, "manual", ?)'
            )->execute([$customerId, $planId, $startsAt, $expiresAt, $sessionsRemaining, $notes]);

            // Email
            $custNameRow = db()->prepare('SELECT name FROM bk_customers WHERE id=?');
            $custNameRow->execute([$customerId]);
            $custName = $custNameRow->fetchColumn() ?: $custEmail;
            $html = "<h2>Your Membership Has Been Activated</h2><p>Hi {$custName},</p><p>Your <strong>{$plan['name']}</strong> membership is now active until <strong>{$expiresAt}</strong>.</p><p><a href=\"" . APP_URL . "/booking/book.php\">Book an appointment →</a></p>";
            $text = "Membership activated: {$plan['name']} — expires {$expiresAt}";
            try { send_mail($custEmail, $custName, "Your Membership Has Been Activated", $html, $text); } catch(\Throwable $e) {}

            json_out(['ok' => true, 'id' => (int) db()->lastInsertId()]);
        }

        if ($method === 'PUT' && $adminId !== null) {
            $sets = []; $params = [];
            if (isset($body['status']))     { $sets[] = 'status=?';     $params[] = $body['status']; }
            if (isset($body['expires_at'])) { $sets[] = 'expires_at=?'; $params[] = $body['expires_at']; }
            if (isset($body['notes']))      { $sets[] = 'notes=?';      $params[] = $body['notes']; }
            if (empty($sets)) json_out(['ok' => false, 'error' => 'Nothing to update'], 400);
            $params[] = (int)$adminId;
            db()->prepare('UPDATE bk_memberships SET ' . implode(',', $sets) . ' WHERE id=?')->execute($params);
            json_out(['ok' => true]);
        }
    }

    // ── Plans CRUD ────────────────────────────────────────────
    if ($adminRes === 'plans') {
        if ($method === 'GET') {
            if ($adminId) {
                $stmt = db()->prepare('SELECT * FROM bk_plans WHERE id=?');
                $stmt->execute([(int)$adminId]);
                $plan = $stmt->fetch();
                if (!$plan) json_out(['ok' => false, 'error' => 'Not found'], 404);
                $svcs = db()->prepare('SELECT service_id FROM bk_plan_services WHERE plan_id=?');
                $svcs->execute([(int)$adminId]);
                $plan['service_ids'] = array_column($svcs->fetchAll(), 'service_id');
                json_out(['ok' => true, 'plan' => $plan]);
            } else {
                $plans = db()->query('SELECT * FROM bk_plans ORDER BY price ASC')->fetchAll();
                foreach ($plans as &$p) {
                    $s = db()->prepare('SELECT service_id FROM bk_plan_services WHERE plan_id=?');
                    $s->execute([$p['id']]);
                    $p['service_ids'] = array_column($s->fetchAll(), 'service_id');
                }
                json_out(['ok' => true, 'plans' => $plans]);
            }
        }

        if ($method === 'POST') {
            $name    = trim($body['name'] ?? '');
            $price   = (float)($body['price'] ?? 0);
            $days    = (int)($body['duration_days'] ?? 30);
            $sessLim = (int)($body['session_limit'] ?? 0);
            $desc    = $body['description'] ?? '';
            $color   = $body['color'] ?? '#e09500';
            $active  = isset($body['is_active']) ? (int)$body['is_active'] : 1;
            $svcIds  = $body['service_ids'] ?? [];
            if (!$name) json_out(['ok' => false, 'error' => 'Name required'], 400);

            db()->prepare(
                'INSERT INTO bk_plans (name, description, price, duration_days, session_limit, color, is_active) VALUES (?,?,?,?,?,?,?)'
            )->execute([$name, $desc, $price, $days, $sessLim, $color, $active]);
            $planId = (int) db()->lastInsertId();

            foreach ((array)$svcIds as $sid) {
                try { db()->prepare('INSERT IGNORE INTO bk_plan_services (plan_id, service_id) VALUES (?,?)')->execute([$planId, (int)$sid]); } catch(\Throwable $e) {}
            }
            json_out(['ok' => true, 'id' => $planId]);
        }

        if ($method === 'PUT' && $adminId) {
            $sets = []; $params = [];
            foreach (['name','description','color'] as $f) {
                if (isset($body[$f])) { $sets[] = "{$f}=?"; $params[] = $body[$f]; }
            }
            foreach (['price','duration_days','session_limit','is_active'] as $f) {
                if (isset($body[$f])) { $sets[] = "{$f}=?"; $params[] = (float)$body[$f]; }
            }
            if ($sets) {
                $params[] = (int)$adminId;
                db()->prepare('UPDATE bk_plans SET ' . implode(',', $sets) . ' WHERE id=?')->execute($params);
            }
            if (isset($body['service_ids'])) {
                db()->prepare('DELETE FROM bk_plan_services WHERE plan_id=?')->execute([(int)$adminId]);
                foreach ((array)$body['service_ids'] as $sid) {
                    try { db()->prepare('INSERT IGNORE INTO bk_plan_services (plan_id, service_id) VALUES (?,?)')->execute([(int)$adminId, (int)$sid]); } catch(\Throwable $e) {}
                }
            }
            json_out(['ok' => true]);
        }

        if ($method === 'DELETE' && $adminId) {
            db()->prepare('DELETE FROM bk_plan_services WHERE plan_id=?')->execute([(int)$adminId]);
            db()->prepare('DELETE FROM bk_plans WHERE id=?')->execute([(int)$adminId]);
            json_out(['ok' => true]);
        }
    }

    // ── Services CRUD ─────────────────────────────────────────
    if ($adminRes === 'services') {
        if ($method === 'GET') {
            if ($adminId) {
                $stmt = db()->prepare('SELECT * FROM bk_services WHERE id=?');
                $stmt->execute([(int)$adminId]);
                $row = $stmt->fetch();
                json_out($row ? ['ok' => true, 'service' => $row] : ['ok' => false, 'error' => 'Not found']);
            } else {
                $rows = db()->query('SELECT * FROM bk_services ORDER BY name')->fetchAll();
                json_out(['ok' => true, 'services' => $rows]);
            }
        }
        if ($method === 'POST') {
            $name   = trim($body['name'] ?? '');
            $dur    = (int)($body['duration_min'] ?? 60);
            $desc   = $body['description'] ?? '';
            $color  = $body['color'] ?? '#e09500';
            $restr  = isset($body['is_restricted']) ? (int)$body['is_restricted'] : 1;
            $active = isset($body['is_active']) ? (int)$body['is_active'] : 1;
            if (!$name) json_out(['ok' => false, 'error' => 'Name required'], 400);
            db()->prepare('INSERT INTO bk_services (name, description, duration_min, color, is_restricted, is_active) VALUES (?,?,?,?,?,?)')
                ->execute([$name, $desc, $dur, $color, $restr, $active]);
            json_out(['ok' => true, 'id' => (int) db()->lastInsertId()]);
        }
        if ($method === 'PUT' && $adminId) {
            $sets = []; $params = [];
            foreach (['name','description','color'] as $f) {
                if (isset($body[$f])) { $sets[] = "{$f}=?"; $params[] = $body[$f]; }
            }
            foreach (['duration_min','is_restricted','is_active'] as $f) {
                if (isset($body[$f])) { $sets[] = "{$f}=?"; $params[] = (int)$body[$f]; }
            }
            if ($sets) {
                $params[] = (int)$adminId;
                db()->prepare('UPDATE bk_services SET ' . implode(',', $sets) . ' WHERE id=?')->execute($params);
            }
            json_out(['ok' => true]);
        }
        if ($method === 'DELETE' && $adminId) {
            db()->prepare('DELETE FROM bk_services WHERE id=?')->execute([(int)$adminId]);
            json_out(['ok' => true]);
        }
    }

    // ── Providers CRUD ────────────────────────────────────────
    if ($adminRes === 'providers') {
        if ($method === 'GET' && $adminId === null) {
            $rows = db()->query('SELECT * FROM bk_providers ORDER BY name')->fetchAll();
            json_out(['ok' => true, 'providers' => $rows]);
        }
        if ($method === 'GET' && $adminId !== null && $adminSub === null) {
            $stmt = db()->prepare('SELECT * FROM bk_providers WHERE id=?');
            $stmt->execute([(int)$adminId]);
            $prov = $stmt->fetch();
            if (!$prov) json_out(['ok' => false, 'error' => 'Not found'], 404);

            $svcs = db()->prepare('SELECT ps.*, s.name as service_name FROM bk_provider_services ps JOIN bk_services s ON s.id=ps.service_id WHERE ps.provider_id=?');
            $svcs->execute([(int)$adminId]);
            $prov['services'] = $svcs->fetchAll();

            $hrs = db()->prepare('SELECT * FROM bk_provider_hours WHERE provider_id=? ORDER BY day_of_week');
            $hrs->execute([(int)$adminId]);
            $prov['hours'] = $hrs->fetchAll();

            json_out(['ok' => true, 'provider' => $prov]);
        }
        if ($method === 'POST' && $adminId === null) {
            $name   = trim($body['name'] ?? '');
            $branch = $body['branch_name'] ?? '';
            $email  = $body['email'] ?? '';
            $phone  = $body['phone'] ?? '';
            $bio    = $body['bio'] ?? '';
            $tz     = $body['timezone'] ?? 'UTC';
            $active = isset($body['is_active']) ? (int)$body['is_active'] : 1;
            if (!$name) json_out(['ok' => false, 'error' => 'Name required'], 400);
            db()->prepare('INSERT INTO bk_providers (name, branch_name, email, phone, bio, timezone, is_active) VALUES (?,?,?,?,?,?,?)')
                ->execute([$name, $branch, $email, $phone, $bio, $tz, $active]);
            $newId = (int) db()->lastInsertId();
            if (isset($body['service_ids'])) {
                $cap = (int)($body['capacity'] ?? 1);
                foreach ((array)$body['service_ids'] as $sid) {
                    try {
                        db()->prepare('INSERT INTO bk_provider_services (provider_id, service_id, capacity_per_slot) VALUES (?,?,?)')
                            ->execute([$newId, (int)$sid, $cap]);
                    } catch(\Throwable $e) {}
                }
            }
            json_out(['ok' => true, 'id' => $newId]);
        }
        if ($method === 'PUT' && $adminId !== null && $adminSub === null) {
            $sets = []; $params = [];
            foreach (['name','branch_name','email','phone','bio','avatar_url','timezone'] as $f) {
                if (isset($body[$f])) { $sets[] = "{$f}=?"; $params[] = $body[$f]; }
            }
            if (isset($body['is_active'])) { $sets[] = 'is_active=?'; $params[] = (int)$body['is_active']; }
            if ($sets) {
                $params[] = (int)$adminId;
                db()->prepare('UPDATE bk_providers SET ' . implode(',', $sets) . ' WHERE id=?')->execute($params);
            }
            // Update services if provided
            if (isset($body['service_ids'])) {
                db()->prepare('DELETE FROM bk_provider_services WHERE provider_id=?')->execute([(int)$adminId]);
                foreach ((array)$body['service_ids'] as $sid) {
                    $cap = (int)($body['capacity'] ?? 1);
                    try { db()->prepare('INSERT INTO bk_provider_services (provider_id, service_id, capacity_per_slot) VALUES (?,?,?)')->execute([(int)$adminId, (int)$sid, $cap]); } catch(\Throwable $e) {}
                }
            }
            json_out(['ok' => true]);
        }
        if ($method === 'DELETE' && $adminId !== null) {
            db()->prepare('DELETE FROM bk_providers WHERE id=?')->execute([(int)$adminId]);
            json_out(['ok' => true]);
        }

        // Provider hours: GET/POST/PUT/DELETE /api/booking/admin/providers/:id/hours
        if ($adminSub === 'hours') {
            $provId = (int)$adminId;
            if ($method === 'GET') {
                $stmt = db()->prepare('SELECT * FROM bk_provider_hours WHERE provider_id=? ORDER BY day_of_week');
                $stmt->execute([$provId]);
                json_out(['ok' => true, 'hours' => $stmt->fetchAll()]);
            }
            if ($method === 'POST') {
                // Upsert all 7 days at once or single day
                $hoursData = $body['hours'] ?? [$body]; // array of {day_of_week, start_time, end_time, is_active}
                foreach ($hoursData as $h) {
                    $dow   = (int)($h['day_of_week'] ?? 0);
                    $start = $h['start_time'] ?? '09:00:00';
                    $end   = $h['end_time'] ?? '17:00:00';
                    $act   = isset($h['is_active']) ? (int)$h['is_active'] : 1;
                    db()->prepare(
                        'INSERT INTO bk_provider_hours (provider_id, day_of_week, start_time, end_time, is_active)
                         VALUES (?,?,?,?,?)
                         ON DUPLICATE KEY UPDATE start_time=VALUES(start_time), end_time=VALUES(end_time), is_active=VALUES(is_active)'
                    )->execute([$provId, $dow, $start, $end, $act]);
                }
                json_out(['ok' => true]);
            }
            if ($method === 'PUT') {
                $dow = isset($body['day_of_week']) ? (int)$body['day_of_week'] : null;
                if ($dow === null || $dow < 0 || $dow > 6) {
                    json_out(['ok' => false, 'error' => 'Valid day_of_week required (0-6)'], 400);
                }
                $start = $body['start_time'] ?? '09:00:00';
                $end   = $body['end_time'] ?? '17:00:00';
                $act   = isset($body['is_active']) ? (int)$body['is_active'] : 1;
                db()->prepare(
                    'INSERT INTO bk_provider_hours (provider_id, day_of_week, start_time, end_time, is_active)
                     VALUES (?,?,?,?,?)
                     ON DUPLICATE KEY UPDATE start_time=VALUES(start_time), end_time=VALUES(end_time), is_active=VALUES(is_active)'
                )->execute([$provId, $dow, $start, $end, $act]);
                json_out(['ok' => true]);
            }
            if ($method === 'DELETE') {
                db()->prepare('DELETE FROM bk_provider_hours WHERE provider_id=?')->execute([$provId]);
                json_out(['ok' => true]);
            }
        }

        // Provider breaks: /api/booking/admin/providers/:id/breaks
        if ($adminSub === 'breaks') {
            $provId = (int)$adminId;
            if ($method === 'GET') {
                $date = $_GET['date'] ?? null;
                if ($date) {
                    $stmt = db()->prepare('SELECT * FROM bk_provider_breaks WHERE provider_id=? AND break_date=? ORDER BY start_time');
                    $stmt->execute([$provId, $date]);
                } else {
                    $stmt = db()->prepare('SELECT * FROM bk_provider_breaks WHERE provider_id=? AND break_date >= CURDATE() ORDER BY break_date, start_time');
                    $stmt->execute([$provId]);
                }
                json_out(['ok' => true, 'breaks' => $stmt->fetchAll()]);
            }
            if ($method === 'POST') {
                $breakDate = $body['break_date'] ?? date('Y-m-d');
                $start     = $body['start_time'] ?? '12:00:00';
                $end       = $body['end_time'] ?? '13:00:00';
                $label     = $body['label'] ?? 'Break';
                db()->prepare('INSERT INTO bk_provider_breaks (provider_id, break_date, start_time, end_time, label) VALUES (?,?,?,?,?)')
                    ->execute([$provId, $breakDate, $start, $end, $label]);
                json_out(['ok' => true, 'id' => (int) db()->lastInsertId()]);
            }
            if ($method === 'PUT') {
                $breakId = (int)($segs[3] ?? 0);
                if (!$breakId) {
                    json_out(['ok' => false, 'error' => 'Break id required in route'], 400);
                }
                $sets = [];
                $params = [];
                foreach (['break_date', 'start_time', 'end_time', 'label'] as $f) {
                    if (array_key_exists($f, $body)) {
                        $sets[] = "{$f}=?";
                        $params[] = $body[$f];
                    }
                }
                if (!$sets) {
                    json_out(['ok' => false, 'error' => 'Nothing to update'], 400);
                }
                $params[] = $breakId;
                $params[] = $provId;
                db()->prepare('UPDATE bk_provider_breaks SET ' . implode(',', $sets) . ' WHERE id=? AND provider_id=?')
                    ->execute($params);
                json_out(['ok' => true]);
            }
            if ($method === 'DELETE') {
                $breakId = (int)($segs[3] ?? 0);
                if ($breakId) {
                    db()->prepare('DELETE FROM bk_provider_breaks WHERE id=? AND provider_id=?')->execute([$breakId, $provId]);
                } else {
                    db()->prepare('DELETE FROM bk_provider_breaks WHERE provider_id=?')->execute([$provId]);
                }
                json_out(['ok' => true]);
            }
        }
    }

    // ── Promo Codes CRUD ──────────────────────────────────────
    if ($adminRes === 'promo-codes') {
        if ($method === 'GET') {
            if ($adminId) {
                $stmt = db()->prepare('SELECT * FROM bk_promo_codes WHERE id=?');
                $stmt->execute([(int)$adminId]);
                $row = $stmt->fetch();
                json_out($row ? ['ok' => true, 'promo' => $row] : ['ok' => false, 'error' => 'Not found'], $row ? 200 : 404);
            }
            $rows = db()->query('SELECT * FROM bk_promo_codes ORDER BY created_at DESC')->fetchAll();
            json_out(['ok' => true, 'promos' => $rows]);
        }
        if ($method === 'POST') {
            $code  = strtoupper(preg_replace('/[^A-Z0-9\-_]/', '', strtoupper(trim($body['code'] ?? ''))));
            $dtype = $body['discount_type'] ?? 'percent';
            $dval  = (float)($body['discount_value'] ?? 0);
            $desc  = $body['description'] ?? '';
            $max   = isset($body['max_uses']) && $body['max_uses'] !== '' ? (int)$body['max_uses'] : null;
            $from  = $body['valid_from'] ?: null;
            $until = $body['valid_until'] ?: null;
            $plans = isset($body['plan_ids']) && is_array($body['plan_ids']) ? json_encode($body['plan_ids']) : null;
            $act   = isset($body['is_active']) ? (int)$body['is_active'] : 1;
            if (!$code || !in_array($dtype, ['percent','fixed'])) json_out(['ok'=>false,'error'=>'Code and valid discount_type required'], 400);
            try {
                db()->prepare(
                    'INSERT INTO bk_promo_codes (code,description,discount_type,discount_value,max_uses,valid_from,valid_until,plan_ids,is_active)
                     VALUES (?,?,?,?,?,?,?,?,?)'
                )->execute([$code, $desc, $dtype, $dval, $max, $from, $until, $plans, $act]);
            } catch (\Throwable $e) {
                json_out(['ok'=>false,'error'=>'Code already exists'], 409);
            }
            json_out(['ok'=>true,'id'=>(int)db()->lastInsertId()]);
        }
        if ($method === 'PUT' && $adminId) {
            $sets=[]; $params=[];
            foreach (['description','discount_type','valid_from','valid_until'] as $f) {
                if (array_key_exists($f,$body)) { $sets[]="{$f}=?"; $params[]=$body[$f]; }
            }
            foreach (['discount_value','max_uses','is_active'] as $f) {
                if (array_key_exists($f,$body)) { $sets[]="{$f}=?"; $params[]=$body[$f]===''?null:(float)$body[$f]; }
            }
            if (array_key_exists('plan_ids',$body)) {
                $sets[]='plan_ids=?';
                $params[]=is_array($body['plan_ids'])&&!empty($body['plan_ids'])?json_encode($body['plan_ids']):null;
            }
            if (!$sets) json_out(['ok'=>false,'error'=>'Nothing to update'], 400);
            $params[]=(int)$adminId;
            db()->prepare('UPDATE bk_promo_codes SET '.implode(',',$sets).' WHERE id=?')->execute($params);
            json_out(['ok'=>true]);
        }
        if ($method === 'DELETE' && $adminId) {
            db()->prepare('DELETE FROM bk_promo_codes WHERE id=?')->execute([(int)$adminId]);
            json_out(['ok'=>true]);
        }
    }

    // ── Admin: send reminders now (cron trigger endpoint) ────
    if ($method === 'POST' && $adminRes === 'send-reminders') {
        $sent24 = 0; $sent1 = 0; $errors = 0;

        // 24h reminders: appointments starting in 23-25h that haven't been reminded
        $stmt24 = db()->query(
            "SELECT a.*, c.name as customer_name, c.email as customer_email,
                    s.name as service_name, p.name as provider_name
             FROM bk_appointments a
             JOIN bk_customers c ON c.id = a.customer_id
             JOIN bk_services s ON s.id = a.service_id
             JOIN bk_providers p ON p.id = a.provider_id
             WHERE a.status IN ('pending','confirmed')
               AND a.start_datetime BETWEEN DATE_ADD(NOW(), INTERVAL 23 HOUR)
                                        AND DATE_ADD(NOW(), INTERVAL 25 HOUR)
               AND a.reminder_24h_sent = 0"
        );
        foreach ($stmt24->fetchAll() as $appt) {
            $apptDate = date('D, d M Y \a\t g:ia', strtotime($appt['start_datetime']));
            $html = "<h2>Reminder: Your appointment tomorrow</h2>
                <p>Hi {$appt['customer_name']},</p>
                <p>This is a reminder that you have an appointment tomorrow.</p>
                <table>
                    <tr><td><strong>Service:</strong></td><td>{$appt['service_name']}</td></tr>
                    <tr><td><strong>Provider:</strong></td><td>{$appt['provider_name']}</td></tr>
                    <tr><td><strong>Date &amp; Time:</strong></td><td>{$apptDate}</td></tr>
                    <tr><td><strong>Reference:</strong></td><td>{$appt['ref']}</td></tr>
                </table>
                <p>Need to cancel? Visit your <a href=\"" . APP_URL . "/customer/dashboard.php\">Member Portal</a>.</p>";
            $text = "Reminder: {$appt['service_name']} with {$appt['provider_name']} — {$apptDate} (Ref: {$appt['ref']})";
            try {
                send_mail($appt['customer_email'], $appt['customer_name'], "Reminder: Your appointment tomorrow — {$appt['service_name']}", $html, $text);
                db()->prepare('UPDATE bk_appointments SET reminder_24h_sent=1 WHERE id=?')->execute([$appt['id']]);
                $sent24++;
            } catch (\Throwable $e) {
                error_log('Reminder 24h error: ' . $e->getMessage()); $errors++;
            }
        }

        // 1h reminders: appointments starting in 55-65 minutes
        $stmt1h = db()->query(
            "SELECT a.*, c.name as customer_name, c.email as customer_email,
                    s.name as service_name, p.name as provider_name
             FROM bk_appointments a
             JOIN bk_customers c ON c.id = a.customer_id
             JOIN bk_services s ON s.id = a.service_id
             JOIN bk_providers p ON p.id = a.provider_id
             WHERE a.status IN ('pending','confirmed')
               AND a.start_datetime BETWEEN DATE_ADD(NOW(), INTERVAL 55 MINUTE)
                                        AND DATE_ADD(NOW(), INTERVAL 65 MINUTE)
               AND a.reminder_1h_sent = 0"
        );
        foreach ($stmt1h->fetchAll() as $appt) {
            $apptDate = date('D, d M Y \a\t g:ia', strtotime($appt['start_datetime']));
            $html = "<h2>Your appointment is in 1 hour</h2>
                <p>Hi {$appt['customer_name']},</p>
                <p>Just a heads-up — your appointment starts in about an hour.</p>
                <table>
                    <tr><td><strong>Service:</strong></td><td>{$appt['service_name']}</td></tr>
                    <tr><td><strong>Provider:</strong></td><td>{$appt['provider_name']}</td></tr>
                    <tr><td><strong>Time:</strong></td><td>{$apptDate}</td></tr>
                </table>";
            $text = "Starting in 1 hour: {$appt['service_name']} with {$appt['provider_name']} — {$apptDate}";
            try {
                send_mail($appt['customer_email'], $appt['customer_name'], "Starting soon: {$appt['service_name']} in 1 hour", $html, $text);
                db()->prepare('UPDATE bk_appointments SET reminder_1h_sent=1 WHERE id=?')->execute([$appt['id']]);
                $sent1++;
            } catch (\Throwable $e) {
                error_log('Reminder 1h error: ' . $e->getMessage()); $errors++;
            }
        }

        json_out(['ok' => true, 'sent_24h' => $sent24, 'sent_1h' => $sent1, 'errors' => $errors]);
    }

    // ── Admin: Ratings ────────────────────────────────────────
    if ($adminRes === 'ratings') {
        if ($method === 'GET') {
            $where = ['1=1']; $params = [];
            if (!empty($_GET['provider_id'])) { $where[] = 'r.provider_id=?'; $params[] = (int)$_GET['provider_id']; }
            $page = max(1,(int)($_GET['page'] ?? 1)); $limit = 50; $offset = ($page-1)*$limit;
            $ws = implode(' AND ',$where);
            $cStmt = db()->prepare("SELECT COUNT(*) FROM bk_ratings r WHERE {$ws}");
            $cStmt->execute($params);
            $total = (int)$cStmt->fetchColumn();
            $stmt = db()->prepare(
                "SELECT r.*, c.name as customer_name, c.email as customer_email,
                        p.name as provider_name, s.name as service_name
                 FROM bk_ratings r
                 JOIN bk_customers c ON c.id = r.customer_id
                 JOIN bk_providers p ON p.id = r.provider_id
                 JOIN bk_services s ON s.id = r.service_id
                 WHERE {$ws} ORDER BY r.created_at DESC LIMIT {$limit} OFFSET {$offset}"
            );
            $stmt->execute($params);
            json_out(['ok'=>true,'ratings'=>$stmt->fetchAll(),'total'=>$total,'page'=>$page,'pages'=>ceil($total/$limit)]);
        }
        if ($method === 'PUT' && $adminId) {
            $sets=[]; $params=[];
            if (isset($body['is_public'])) { $sets[]='is_public=?'; $params[]=(int)$body['is_public']; }
            if (!$sets) json_out(['ok'=>false,'error'=>'Nothing to update'],400);
            $params[]=(int)$adminId;
            db()->prepare('UPDATE bk_ratings SET '.implode(',',$sets).' WHERE id=?')->execute($params);
            json_out(['ok'=>true]);
        }
        if ($method === 'DELETE' && $adminId) {
            db()->prepare('DELETE FROM bk_ratings WHERE id=?')->execute([(int)$adminId]);
            json_out(['ok'=>true]);
        }
    }

    // ── Admin: Session adjustment (POST /admin/memberships/:id/adjust-sessions)
    if ($adminRes === 'memberships' && $adminSub === 'adjust-sessions' && $adminId && $method === 'POST') {
        $delta = (int)($body['delta'] ?? 0);
        $note  = trim($body['note'] ?? '');
        if ($delta === 0) json_out(['ok' => false, 'error' => 'delta cannot be zero'], 400);
        if (!$note)       json_out(['ok' => false, 'error' => 'note is required'], 400);

        $memRow = db()->prepare('SELECT sessions_remaining FROM bk_memberships WHERE id=?');
        $memRow->execute([(int)$adminId]);
        $mem = $memRow->fetch();
        if (!$mem) json_out(['ok' => false, 'error' => 'Membership not found'], 404);

        $current = $mem['sessions_remaining'];
        if ($current === null) {
            json_out(['ok' => false, 'error' => 'Cannot adjust sessions on an unlimited plan'], 400);
        }

        $newVal = max(0, (int)$current + $delta);
        db()->beginTransaction();
        try {
            db()->prepare('UPDATE bk_memberships SET sessions_remaining=? WHERE id=?')
                ->execute([$newVal, (int)$adminId]);

            $adminUserId = (int)($_SESSION['admin_user_id'] ?? 0);
            db()->prepare(
                'INSERT INTO bk_membership_log
                 (membership_id, admin_id, action, sessions_delta, note)
                 VALUES (?, ?, "adjust", ?, ?)'
            )->execute([(int)$adminId, $adminUserId ?: null, $delta, $note]);

            db()->commit();
            json_out(['ok' => true, 'sessions_remaining' => $newVal]);
        } catch (\Throwable $e) {
            db()->rollBack();
            json_out(['ok' => false, 'error' => 'Adjustment failed: ' . $e->getMessage()], 500);
        }
    }

    // ── Admin: Check-in history (GET /admin/checkins) ─────────
    if ($adminRes === 'checkins' && $method === 'GET') {
        $page   = max(1, (int)($_GET['page'] ?? 1));
        $limit  = 50;
        $offset = ($page - 1) * $limit;

        $where  = ['1=1'];
        $params = [];

        if (!empty($_GET['date'])) {
            $where[]  = 'DATE(ci.checked_in_at) = ?';
            $params[] = $_GET['date'];
        }
        if (!empty($_GET['method']) && in_array($_GET['method'], ['qr','manual'])) {
            $where[]  = 'ci.method = ?';
            $params[] = $_GET['method'];
        }

        $ws = implode(' AND ', $where);

        $cntStmt = db()->prepare(
            "SELECT COUNT(*) FROM bk_checkins ci WHERE {$ws}"
        );
        $cntStmt->execute($params);
        $total = (int)$cntStmt->fetchColumn();

        $stmt = db()->prepare(
            "SELECT ci.id, ci.method, ci.checked_in_at,
                    ci.sessions_remaining AS sessions_after_checkin,
                    c.name  AS customer_name,
                    c.email AS customer_email,
                    m.sessions_remaining AS membership_sessions_remaining,
                    p.name  AS plan_name,
                    u.username AS admin_name
             FROM bk_checkins ci
             LEFT JOIN bk_customers c ON c.id = ci.customer_id
             LEFT JOIN bk_memberships m ON m.id = ci.membership_id
             LEFT JOIN bk_plans p ON p.id = m.plan_id
             LEFT JOIN fb_users u ON u.id = ci.scanned_by
             WHERE {$ws}
             ORDER BY ci.checked_in_at DESC
             LIMIT {$limit} OFFSET {$offset}"
        );
        $stmt->execute($params);
        $rows = $stmt->fetchAll();

        // Use snapshot from checkin time; fall back to current membership value
        foreach ($rows as &$row) {
            $row['sessions_remaining'] = $row['sessions_after_checkin'] ?? $row['membership_sessions_remaining'];
        }

        json_out([
            'ok'       => true,
            'checkins' => $rows,
            'total'    => $total,
            'page'     => $page,
            'pages'    => (int)ceil($total / $limit),
        ]);
    }

    // ── Admin: Membership freeze/unfreeze (original block) ────
    if ($adminRes === 'memberships' && $adminSub === 'freeze' && $adminId) {
        $memRow = db()->prepare('SELECT * FROM bk_memberships WHERE id=?');
        $memRow->execute([(int)$adminId]);
        $mem = $memRow->fetch();
        if (!$mem) json_out(['ok'=>false,'error'=>'Not found'],404);
        if ($method === 'POST') {
            // Freeze membership
            if ($mem['frozen_at']) json_out(['ok'=>false,'error'=>'Already frozen'],400);
            db()->prepare('UPDATE bk_memberships SET frozen_at=NOW(), status="active" WHERE id=?')->execute([(int)$adminId]);
            json_out(['ok'=>true,'frozen_at'=>date('Y-m-d H:i:s')]);
        }
        if ($method === 'DELETE') {
            // Unfreeze — extend expiry by frozen days
            if (!$mem['frozen_at']) json_out(['ok'=>false,'error'=>'Not frozen'],400);
            $frozenDays = (int)ceil((time() - strtotime($mem['frozen_at'])) / 86400);
            $newExpiry = date('Y-m-d H:i:s', strtotime($mem['expires_at']) + $frozenDays * 86400);
            db()->prepare('UPDATE bk_memberships SET frozen_at=NULL, frozen_days=frozen_days+?, expires_at=? WHERE id=?')
                ->execute([$frozenDays, $newExpiry, (int)$adminId]);
            json_out(['ok'=>true,'new_expires_at'=>$newExpiry,'days_restored'=>$frozenDays]);
        }
    }

    // ── Admin: Waitlist notify ─────────────────────────────────
    if ($method === 'POST' && $adminRes === 'waitlist' && $adminId && $adminSub === 'notify') {
        $wlRow = db()->prepare(
            'SELECT w.*, s.name as service_name, p.name as provider_name
             FROM bk_waitlist w
             JOIN bk_services s ON s.id = w.service_id
             JOIN bk_providers p ON p.id = w.provider_id
             WHERE w.id=?'
        );
        $wlRow->execute([(int)$adminId]);
        $wl = $wlRow->fetch();
        if (!$wl) json_out(['ok'=>false,'error'=>'Waitlist entry not found'],404);

        $slotDate = date('D, d M Y \a\t g:ia', strtotime($wl['slot_datetime']));
        $bookUrl  = APP_URL . '/book.php';
        $html = "<h2>A spot just opened up!</h2>
            <p>Hi {$wl['customer_name']},</p>
            <p>Good news — a slot you were waiting for is now available.</p>
            <table>
                <tr><td><strong>Service:</strong></td><td>{$wl['service_name']}</td></tr>
                <tr><td><strong>Provider:</strong></td><td>{$wl['provider_name']}</td></tr>
                <tr><td><strong>Slot:</strong></td><td>{$slotDate}</td></tr>
            </table>
            <p><a href=\"{$bookUrl}\">Book now →</a> (slots fill up fast!)</p>";
        $text = "Slot available: {$wl['service_name']} with {$wl['provider_name']} — {$slotDate}. Book at: {$bookUrl}";
        try {
            send_mail($wl['customer_email'], $wl['customer_name'], "Spot available: {$wl['service_name']} with {$wl['provider_name']}", $html, $text);
            json_out(['ok'=>true,'emailed'=>$wl['customer_email']]);
        } catch (\Throwable $e) {
            json_out(['ok'=>false,'error'=>$e->getMessage()],500);
        }
    }


    if ($adminRes === 'customers') {
        if ($method === 'GET') {
            $search = $_GET['search'] ?? '';
            if ($search) {
                $like = '%' . $search . '%';
                $stmt = db()->prepare('SELECT * FROM bk_customers WHERE email LIKE ? OR name LIKE ? ORDER BY name LIMIT 50');
                $stmt->execute([$like, $like]);
            } else {
                $stmt = db()->query('SELECT * FROM bk_customers ORDER BY created_at DESC LIMIT 100');
            }
            json_out(['ok' => true, 'customers' => $stmt->fetchAll()]);
        }
    }

    // ── Waitlist ──────────────────────────────────────────────
    if ($adminRes === 'waitlist') {
        if ($method === 'GET') {
            $where = ['1=1']; $params = [];
            if (!empty($_GET['provider_id'])) { $where[] = 'w.provider_id=?'; $params[] = (int)$_GET['provider_id']; }
            if (!empty($_GET['slot_datetime'])) { $where[] = 'w.slot_datetime=?'; $params[] = $_GET['slot_datetime']; }
            $whereStr = implode(' AND ', $where);
            $stmt = db()->prepare(
                "SELECT w.*, s.name as service_name, p.name as provider_name
                 FROM bk_waitlist w
                 JOIN bk_services s ON s.id = w.service_id
                 JOIN bk_providers p ON p.id = w.provider_id
                 WHERE {$whereStr} ORDER BY w.created_at DESC"
            );
            $stmt->execute($params);
            json_out(['ok' => true, 'waitlist' => $stmt->fetchAll()]);
        }
        if ($method === 'DELETE' && $adminId) {
            db()->prepare('DELETE FROM bk_waitlist WHERE id=?')->execute([(int)$adminId]);
            json_out(['ok' => true]);
        }
    }
}

// ── 404 fallback ─────────────────────────────────────────────
json_out(['ok' => false, 'error' => 'Unknown route: ' . $method . ' /' . implode('/', $segs)], 404);

} catch (\Throwable $e) {
    error_log('Booking API error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
    json_out(['ok' => false, 'error' => 'Server error: ' . $e->getMessage()], 500);
}
