<?php
// ─────────────────────────────────────────────────────────────
//  lib/BookingEngine.php — Slot availability & access engine
// ─────────────────────────────────────────────────────────────
if (!defined('DB_HOST')) require_once dirname(__DIR__) . '/lib.php';

class BookingEngine {

    /**
     * Get available time slots for a provider+service on a given date.
     * Returns array of slot objects.
     */
    public static function getSlots(
        int $providerId,
        int $serviceId,
        string $date  // 'Y-m-d'
    ): array {
        // 1. Get provider working hours for this day_of_week
        $dow = (int) date('w', strtotime($date)); // 0=Sun
        $hours = db()->prepare(
            'SELECT start_time, end_time FROM bk_provider_hours
             WHERE provider_id=? AND day_of_week=? AND is_active=1'
        );
        $hours->execute([$providerId, $dow]);
        $h = $hours->fetch();
        if (!$h) return []; // provider not working this day

        // 2. Get service duration + provider capacity
        $ps = db()->prepare(
            'SELECT ps.capacity_per_slot, s.duration_min
             FROM bk_provider_services ps
             JOIN bk_services s ON s.id = ps.service_id
             WHERE ps.provider_id=? AND ps.service_id=?'
        );
        $ps->execute([$providerId, $serviceId]);
        $config = $ps->fetch();
        if (!$config) return [];

        $duration = (int) $config['duration_min'];
        $capacity = (int) $config['capacity_per_slot'];

        // 3. Generate all possible slot start times
        $start = strtotime($date . ' ' . $h['start_time']);
        $end   = strtotime($date . ' ' . $h['end_time']);
        $now   = time();
        $slots = [];
        $cursor = $start;
        while ($cursor + $duration * 60 <= $end) {
            $slots[] = $cursor;
            $cursor += $duration * 60;
        }

        // 4. Get breaks for this date
        $breaks = db()->prepare(
            'SELECT start_time, end_time FROM bk_provider_breaks
             WHERE provider_id=? AND break_date=?'
        );
        $breaks->execute([$providerId, $date]);
        $breakList = $breaks->fetchAll();

        // 5. Get existing appointment counts per slot
        $appts = db()->prepare(
            'SELECT start_datetime, COUNT(*) as cnt
             FROM bk_appointments
             WHERE provider_id=? AND service_id=?
               AND DATE(start_datetime)=?
               AND status NOT IN ("cancelled","no_show")
             GROUP BY start_datetime'
        );
        $appts->execute([$providerId, $serviceId, $date]);
        $bookedMap = [];
        foreach ($appts->fetchAll() as $a) {
            $bookedMap[strtotime($a['start_datetime'])] = (int)$a['cnt'];
        }

        // 6. Build result — filter past, filter breaks
        $result = [];
        foreach ($slots as $ts) {
            if ($ts <= $now) continue; // past

            // Check break overlap
            $slotTime = date('H:i:s', $ts);
            $slotEnd  = date('H:i:s', $ts + $duration * 60);
            $inBreak = false;
            foreach ($breakList as $b) {
                if ($slotTime < $b['end_time'] && $slotEnd > $b['start_time']) {
                    $inBreak = true; break;
                }
            }
            if ($inBreak) continue;

            $booked    = $bookedMap[$ts] ?? 0;
            $available = $booked < $capacity;

            $result[] = [
                'time'      => date('H:i', $ts),
                'datetime'  => date('Y-m-d H:i:s', $ts),
                'timestamp' => $ts,
                'available' => $available,
                'booked'    => $booked,
                'capacity'  => $capacity,
                'full'      => !$available,
            ];
        }

        return $result;
    }

    /**
     * Check if a customer email has an active membership that unlocks a service.
     */
    public static function checkAccess(string $email, int $serviceId): array {
        // Get customer
        $cust = db()->prepare('SELECT * FROM bk_customers WHERE email=?');
        $cust->execute([$email]);
        $customer = $cust->fetch();
        if (!$customer) return ['allowed' => false, 'reason' => 'no_customer'];

        // Get active memberships
        $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) return ['allowed' => false, 'reason' => 'no_membership'];

        // Check service is in plan
        $svc = db()->prepare(
            'SELECT 1 FROM bk_plan_services
             WHERE plan_id=? AND service_id=?'
        );
        $svc->execute([$membership['plan_id'], $serviceId]);
        if (!$svc->fetch()) {
            return ['allowed' => false, 'reason' => 'service_not_in_plan'];
        }

        return [
            'allowed'    => true,
            'customer'   => $customer,
            'membership' => $membership,
        ];
    }

    /**
     * Get all services unlocked by a customer's active membership.
     */
    public static function getUnlockedServices(string $email): array {
        $cust = db()->prepare('SELECT id FROM bk_customers WHERE email=?');
        $cust->execute([$email]);
        $customer = $cust->fetch();
        if (!$customer) return [];

        $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) return [];

        $svcs = db()->prepare(
            'SELECT s.id, s.name, s.duration_min, s.color
             FROM bk_plan_services ps
             JOIN bk_services s ON s.id = ps.service_id
             WHERE ps.plan_id=? AND s.is_active=1
             ORDER BY s.name'
        );
        $svcs->execute([$membership['plan_id']]);
        $res = $svcs->fetchAll();
        return $res;
    }

    /**
     * Check if a specific slot is still available (for race-condition protection).
     * Must be called inside a transaction.
     */
    public static function isSlotAvailable(
        int $providerId,
        int $serviceId,
        string $datetime
    ): bool {
        $ps = db()->prepare(
            'SELECT ps.capacity_per_slot FROM bk_provider_services ps
             WHERE ps.provider_id=? AND ps.service_id=?'
        );
        $ps->execute([$providerId, $serviceId]);
        $config = $ps->fetch();
        if (!$config) return false;

        $capacity = (int) $config['capacity_per_slot'];

        $cnt = db()->prepare(
            'SELECT COUNT(*) FROM bk_appointments
             WHERE provider_id=? AND service_id=? AND start_datetime=?
               AND status NOT IN ("cancelled","no_show")'
        );
        $cnt->execute([$providerId, $serviceId, $datetime]);
        $booked = (int) $cnt->fetchColumn();

        return $booked < $capacity;
    }

    /**
     * Generate a booking reference.
     */
    public static function genRef(): string {
        return 'BK-' . strtoupper(base_convert((string)time(), 10, 36))
             . '-' . strtoupper(substr(bin2hex(random_bytes(3)), 0, 6));
    }
}
