<?php

require_once BASE_PATH . '/config/database.php';
require_once BASE_PATH . '/app/models/BookingStatusLog.php';
require_once BASE_PATH . '/app/models/Notification.php';
require_once BASE_PATH . '/app/models/User.php';

class Booking
{
    private PDO $db;
    private BookingStatusLog $statusLogs;
    private Notification $notifications;

    public function __construct()
    {
        $this->db = (new Database())->connect();
        $this->statusLogs = new BookingStatusLog();
        $this->notifications = new Notification();
    }

    public function createForCustomer(int $customerId, array $input): array
    {
        $serviceId = (int) ($input['service_id'] ?? 0);
        $date = trim((string) ($input['booking_date'] ?? ''));
        $time = trim((string) ($input['start_time'] ?? ''));
        $petName = trim((string) ($input['pet_name'] ?? ''));
        $petType = trim((string) ($input['pet_type'] ?? ''));
        $petBreed = trim((string) ($input['pet_breed'] ?? '')) ?: null;
        $petWeightInput = trim((string) ($input['pet_weight'] ?? ''));
        $petNotes = trim((string) ($input['pet_notes'] ?? '')) ?: null;
        $notes = trim((string) ($input['notes'] ?? '')) ?: null;

        if ($serviceId < 1 || $petName === '' || !in_array($petType, ['dog', 'cat', 'other'], true)) {
            throw new InvalidArgumentException('Vui lòng chọn dịch vụ và nhập thông tin thú cưng.');
        }
        if ($this->textLength($petName) > 120
            || ($petBreed !== null && $this->textLength($petBreed) > 120)
            || ($petNotes !== null && $this->textLength($petNotes) > 1000)
            || ($notes !== null && $this->textLength($notes) > 1000)) {
            throw new InvalidArgumentException('Thông tin thú cưng hoặc ghi chú vượt quá độ dài cho phép.');
        }
        if ($petWeightInput !== '' && (!is_numeric($petWeightInput) || (float) $petWeightInput <= 0 || (float) $petWeightInput > 9999.99)) {
            throw new InvalidArgumentException('Cân nặng thú cưng phải là số lớn hơn 0.');
        }
        $petWeight = $petWeightInput === '' ? null : number_format((float) $petWeightInput, 2, '.', '');

        $start = $this->buildStart($date, $time);

        if ($start < new DateTimeImmutable('today')) {
            throw new InvalidArgumentException('Không thể đặt lịch cho ngày đã qua.');
        }

        $lockName = 'pet_spa_booking_' . $date;
        if (!$this->acquireDateLock($lockName)) {
            throw new RuntimeException('Hệ thống đang xử lý lịch hẹn cùng ngày. Vui lòng thử lại.');
        }
        try {
            $this->db->beginTransaction();
            $service = $this->findActiveServiceForUpdate($serviceId);
            if (!$service) {
                throw new InvalidArgumentException('Dịch vụ không còn khả dụng.');
            }

            $end = $start->modify('+' . (int) $service['duration_minutes'] . ' minutes');
            $this->assertDuringBusinessHours($date, $start, $end);
            $this->assertNoBlockedSlot($date, $start->format('H:i:s'), $end->format('H:i:s'));
            $this->assertNoBookingConflict($date, $start->format('H:i:s'), $end->format('H:i:s'));

            $code = $this->generateCode();
            $stmt = $this->db->prepare(
                'INSERT INTO bookings (
                    booking_code, customer_id, service_id, service_name_snapshot,
                    service_price_snapshot, duration_minutes_snapshot,
                    pet_name, pet_type, pet_breed, pet_weight, pet_note,
                    booking_date, start_time, end_time, customer_note, status
                 ) VALUES (
                    :booking_code, :customer_id, :service_id, :service_name,
                    :service_price, :duration_minutes,
                    :pet_name, :pet_type, :pet_breed, :pet_weight, :pet_note,
                    :booking_date, :start_time, :end_time, :customer_note, "pending"
                 )'
            );
            $stmt->execute([
                'booking_code' => $code,
                'customer_id' => $customerId,
                'service_id' => $serviceId,
                'service_name' => $service['name'],
                'service_price' => $service['price'],
                'duration_minutes' => $service['duration_minutes'],
                'pet_name' => $petName,
                'pet_type' => $petType,
                'pet_breed' => $petBreed,
                'pet_weight' => $petWeight,
                'pet_note' => $petNotes,
                'booking_date' => $date,
                'start_time' => $start->format('H:i:s'),
                'end_time' => $end->format('H:i:s'),
                'customer_note' => $notes
            ]);
            $bookingId = (int) $this->db->lastInsertId();
            $this->statusLogs->create($bookingId, $customerId, null, 'pending', 'Khách hàng tạo lịch hẹn.');
            $this->notifications->create(
    $customerId,
    'booking.created',
    'Đã gửi yêu cầu đặt lịch',
    'Lịch '
        . $service['name']
        . ' cho '
        . $petName
        . ' đang chờ Spa xác nhận.',
    '?url=customer/booking&id=' . $bookingId
);
      $this->notifyActiveOwners(
    'booking.created',
    'Có lịch hẹn mới',
    'Khách hàng vừa đặt '
        . $service['name']
        . ' cho '
        . $petName
        . '. Lịch đang chờ xác nhận.',
    '?url=owner/booking&id=' . $bookingId
);
            $this->db->commit();

            return $this->findForCustomer($bookingId, $customerId) ?? [];
        } catch (Throwable $exception) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }
            throw $exception;
        } finally {
            $this->releaseDateLock($lockName);
        }
    }

    public function getAvailableSlots(int $serviceId, string $date): array
    {
        $service = $this->findActiveService($serviceId);
        $startOfDay = $this->buildStart($date, '00:00');
        if (!$service || $startOfDay < new DateTimeImmutable('today')) {
            return [];
        }

        $hour = $this->getBusinessHour($date);
        if (!$hour || !(int) $hour['is_open']) {
            return [];
        }

        $open = $this->buildStart($date, substr((string) $hour['open_time'], 0, 5));
        $close = $this->buildStart($date, substr((string) $hour['close_time'], 0, 5));
        $duration = (int) $service['duration_minutes'];
        $now = new DateTimeImmutable();
        $slots = [];

        for ($cursor = $open; $cursor->modify('+' . $duration . ' minutes') <= $close; $cursor = $cursor->modify('+30 minutes')) {
            $end = $cursor->modify('+' . $duration . ' minutes');
            if ($date === $now->format('Y-m-d') && $cursor <= $now) {
                continue;
            }
            if ($this->hasBlockedSlot($date, $cursor->format('H:i:s'), $end->format('H:i:s'))
                || $this->hasBookingConflict($date, $cursor->format('H:i:s'), $end->format('H:i:s'))) {
                continue;
            }
            $slots[] = $cursor->format('H:i');
        }

        return $slots;
    }

    public function getForCustomer(int $customerId, string $status = '', int $limit = 20, int $offset = 0): array
    {
        $where = 'b.customer_id = :customer_id';
        $params = ['customer_id' => $customerId];
        if (in_array($status, $this->statuses(), true)) {
            $where .= ' AND b.status = :status';
            $params['status'] = $status;
        }
        $limit = max(1, min($limit, 100));
        $offset = max(0, $offset);
        $stmt = $this->db->prepare(
            "SELECT b.* FROM bookings b WHERE {$where}
             ORDER BY b.booking_date DESC, b.start_time DESC, b.id DESC
             LIMIT {$limit} OFFSET {$offset}"
        );
        $stmt->execute($params);
        return $stmt->fetchAll();
    }

    public function countForCustomer(int $customerId, string $status = ''): int
    {
        $where = 'customer_id = :customer_id';
        $params = ['customer_id' => $customerId];
        if (in_array($status, $this->statuses(), true)) {
            $where .= ' AND status = :status';
            $params['status'] = $status;
        }
        $stmt = $this->db->prepare("SELECT COUNT(*) FROM bookings WHERE {$where}");
        $stmt->execute($params);
        return (int) $stmt->fetchColumn();
    }

    public function findForCustomer(int $bookingId, int $customerId): ?array
    {
        $stmt = $this->db->prepare(
            'SELECT b.*, s.featured_image, sc.name AS category_name
             FROM bookings b
             LEFT JOIN services s ON s.id = b.service_id
             LEFT JOIN service_categories sc ON sc.id = s.category_id
             WHERE b.id = :id AND b.customer_id = :customer_id LIMIT 1'
        );
        $stmt->execute(['id' => $bookingId, 'customer_id' => $customerId]);
        $booking = $stmt->fetch();
        return $booking ?: null;
    }

    public function cancelByCustomer(int $bookingId, int $customerId): bool
    {
        $this->db->beginTransaction();
        try {
            $stmt = $this->db->prepare(
                'SELECT * FROM bookings WHERE id = :id AND customer_id = :customer_id FOR UPDATE'
            );
            $stmt->execute(['id' => $bookingId, 'customer_id' => $customerId]);
            $booking = $stmt->fetch();
            if (!$booking || $booking['status'] !== 'pending') {
                throw new InvalidArgumentException('Chỉ có thể hủy lịch đang chờ xác nhận.');
            }
            $this->db->prepare(
                'UPDATE bookings SET status = "cancelled", cancelled_at = NOW() WHERE id = :id'
            )->execute(['id' => $bookingId]);
            $this->statusLogs->create($bookingId, $customerId, 'pending', 'cancelled', 'Khách hàng hủy lịch.');
            $this->notifyActiveOwners(
    'booking.cancelled_by_customer',
    'Khách vừa hủy lịch hẹn',
    'Lịch '
        . ($booking['service_name_snapshot'] ?? 'Dịch vụ Spa')
        . ' cho '
        . ($booking['pet_name'] ?? 'thú cưng')
        . ' đã được khách hàng hủy.',
    '?url=owner/booking&id=' . $bookingId
);
            $this->db->commit();
            return true;
        } catch (Throwable $exception) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }
            throw $exception;
        }
    }

    public function getForOwner(array $filters = [], int $limit = 50, int $offset = 0): array
    {
        [$where, $params] = $this->ownerFilter($filters);
        $limit = max(1, min($limit, 100));
        $offset = max(0, $offset);
        $stmt = $this->db->prepare(
            "SELECT b.*, u.full_name AS customer_name, u.email AS customer_email, u.phone AS customer_phone
             FROM bookings b JOIN users u ON u.id = b.customer_id
             WHERE {$where}
             ORDER BY

                /* Lịch từ thời điểm hiện tại trở đi nằm trước */
                CASE
                    WHEN TIMESTAMP(
                        b.booking_date,
                        b.start_time
                    ) >= NOW()
                    THEN 0
                    ELSE 1
                END ASC,

                /* Lịch sắp tới: thời gian gần hiện tại nhất lên trước */
                CASE
                    WHEN TIMESTAMP(
                        b.booking_date,
                        b.start_time
                    ) >= NOW()
                    THEN TIMESTAMP(
                        b.booking_date,
                        b.start_time
                    )
                END ASC,

                /* Lịch đã qua: thời gian gần hiện tại nhất lên trước */
                CASE
                    WHEN TIMESTAMP(
                        b.booking_date,
                        b.start_time
                    ) < NOW()
                    THEN TIMESTAMP(
                        b.booking_date,
                        b.start_time
                    )
                END DESC,

                b.id DESC
             LIMIT {$limit} OFFSET {$offset}"
        );
        $stmt->execute($params);
        return $stmt->fetchAll();
    }

    public function countForOwner(array $filters = []): int
    {
        [$where, $params] = $this->ownerFilter($filters);
        $stmt = $this->db->prepare("SELECT COUNT(*) FROM bookings b WHERE {$where}");
        $stmt->execute($params);
        return (int) $stmt->fetchColumn();
    }

    public function findForOwner(int $bookingId): ?array
    {
        $stmt = $this->db->prepare(
            'SELECT b.*, u.full_name AS customer_name, u.email AS customer_email, u.phone AS customer_phone
             FROM bookings b JOIN users u ON u.id = b.customer_id WHERE b.id = :id LIMIT 1'
        );
        $stmt->execute(['id' => $bookingId]);
        $booking = $stmt->fetch();
        return $booking ?: null;
    }

    public function updateStatus(int $bookingId, int $actorId, string $newStatus, ?string $ownerNote = null): array
    {
        if (!in_array($newStatus, $this->statuses(), true)) {
            throw new InvalidArgumentException('Trạng thái không hợp lệ.');
        }
        if ($ownerNote !== null && $this->textLength($ownerNote) > 1000) {
            throw new InvalidArgumentException('Ghi chú không được vượt quá 1000 ký tự.');
        }
        $this->db->beginTransaction();
        try {
            $stmt = $this->db->prepare('SELECT * FROM bookings WHERE id = :id FOR UPDATE');
            $stmt->execute(['id' => $bookingId]);
            $booking = $stmt->fetch();
            if (!$booking) {
                throw new InvalidArgumentException('Không tìm thấy lịch hẹn.');
            }
            $allowed = [
                'pending' => ['confirmed', 'cancelled'],
                'confirmed' => ['in_progress', 'cancelled'],
                'in_progress' => ['completed'],
                'completed' => [],
                'cancelled' => []
            ];
            if (!in_array($newStatus, $allowed[$booking['status']], true)) {
                throw new InvalidArgumentException('Không thể chuyển trạng thái lịch hẹn theo cách này.');
            }
            $timeColumn = [
                'confirmed' => 'confirmed_at', 'in_progress' => 'started_at',
                'completed' => 'completed_at', 'cancelled' => 'cancelled_at'
            ][$newStatus];
            $this->db->prepare(
                "UPDATE bookings SET status = :status, {$timeColumn} = NOW(),
                 owner_note = COALESCE(:owner_note, owner_note) WHERE id = :id"
            )->execute([
                'status' => $newStatus,
                'owner_note' => $ownerNote,
                'id' => $bookingId
            ]);
            $this->statusLogs->create($bookingId, $actorId, $booking['status'], $newStatus, $ownerNote);
            $this->notifyStatus($booking, $newStatus);
            $this->db->commit();
            return $this->findForOwner($bookingId) ?? [];
        } catch (Throwable $exception) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }
            throw $exception;
        }
    }

    public function updateOwnerNote(int $bookingId, string $note): void
    {
        if ($this->textLength($note) > 1000) {
            throw new InvalidArgumentException('Ghi chú không được vượt quá 1000 ký tự.');
        }
        $stmt = $this->db->prepare('UPDATE bookings SET owner_note = :note WHERE id = :id');
        $stmt->execute(['note' => $note ?: null, 'id' => $bookingId]);
        if ($stmt->rowCount() === 0) {
            throw new InvalidArgumentException('Không tìm thấy lịch hẹn.');
        }
    }

    public function customerSummary(int $customerId): array
    {
        $stmt = $this->db->prepare(
            'SELECT
                SUM(status IN ("pending", "confirmed", "in_progress")) AS active_count,
                SUM(status = "completed") AS completed_count,
                MIN(CASE WHEN status IN ("pending", "confirmed") AND booking_date >= CURDATE()
                    THEN CONCAT(booking_date, " ", start_time) END) AS next_at
             FROM bookings WHERE customer_id = :customer_id'
        );
        $stmt->execute(['customer_id' => $customerId]);
        return $stmt->fetch() ?: [];
    }

    public function ownerSummary(): array
    {
        $row = $this->db->query(
            'SELECT
                SUM(booking_date = CURDATE() AND status IN ("pending", "confirmed", "in_progress")) AS today_count,
                SUM(status = "pending") AS pending_count,
                SUM(status = "in_progress") AS in_progress_count,
                SUM(status = "completed" AND booking_date = CURDATE()) AS completed_today
             FROM bookings'
        )->fetch();
        return $row ?: [];
    }

    public function recentForOwner(int $limit = 8): array
    {
        return $this->getForOwner([], $limit);
    }

    private function findActiveService(int $serviceId): ?array
    {
        $stmt = $this->db->prepare(
            'SELECT s.* FROM services s LEFT JOIN service_categories sc ON sc.id = s.category_id
             WHERE s.id = :id AND s.is_active = 1
             AND (s.category_id IS NULL OR sc.is_active = 1) LIMIT 1'
        );
        $stmt->execute(['id' => $serviceId]);
        $row = $stmt->fetch();
        return $row ?: null;
    }

    private function findActiveServiceForUpdate(int $serviceId): ?array
    {
        $stmt = $this->db->prepare(
            'SELECT s.* FROM services s LEFT JOIN service_categories sc ON sc.id = s.category_id
             WHERE s.id = :id AND s.is_active = 1
             AND (s.category_id IS NULL OR sc.is_active = 1) FOR UPDATE'
        );
        $stmt->execute(['id' => $serviceId]);
        $row = $stmt->fetch();
        return $row ?: null;
    }

    private function getBusinessHour(string $date): ?array
    {
        $day = (int) (new DateTimeImmutable($date))->format('w');
        $stmt = $this->db->prepare('SELECT * FROM business_hours WHERE day_of_week = :day LIMIT 1');
        $stmt->execute(['day' => $day]);
        $row = $stmt->fetch();
        return $row ?: null;
    }

    private function assertDuringBusinessHours(string $date, DateTimeImmutable $start, DateTimeImmutable $end): void
    {
        $hour = $this->getBusinessHour($date);
        if (!$hour || !(int) $hour['is_open'] || !$hour['open_time'] || !$hour['close_time']) {
            throw new InvalidArgumentException('Cửa hàng không phục vụ vào ngày đã chọn.');
        }
        $open = $this->buildStart($date, substr($hour['open_time'], 0, 5));
        $close = $this->buildStart($date, substr($hour['close_time'], 0, 5));
        if ($start < $open || $end > $close) {
            throw new InvalidArgumentException('Khung giờ phải nằm trong giờ mở cửa.');
        }
    }

    private function assertNoBlockedSlot(string $date, string $start, string $end): void
    {
        if ($this->hasBlockedSlot($date, $start, $end)) {
            throw new InvalidArgumentException('Khung giờ này đã được chủ cửa hàng khóa.');
        }
    }

    private function hasBlockedSlot(string $date, string $start, string $end): bool
    {
        $stmt = $this->db->prepare(
            'SELECT 1 FROM blocked_slots
             WHERE blocked_date = :booking_date
             AND (start_time IS NULL OR (start_time < :end_time AND end_time > :start_time))
             LIMIT 1'
        );
        $stmt->execute(['booking_date' => $date, 'start_time' => $start, 'end_time' => $end]);
        return (bool) $stmt->fetchColumn();
    }

    private function assertNoBookingConflict(string $date, string $start, string $end): void
    {
        if ($this->hasBookingConflict($date, $start, $end, true)) {
            throw new InvalidArgumentException('Khung giờ này vừa có người đặt. Vui lòng chọn giờ khác.');
        }
    }

    private function hasBookingConflict(string $date, string $start, string $end, bool $lock = false): bool
    {
        $sql = 'SELECT 1 FROM bookings
                WHERE booking_date = :booking_date
                AND status IN ("pending", "confirmed", "in_progress")
                AND start_time < :end_time AND end_time > :start_time
                LIMIT 1';
        if ($lock) {
            $sql .= ' FOR UPDATE';
        }
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['booking_date' => $date, 'start_time' => $start, 'end_time' => $end]);
        return (bool) $stmt->fetchColumn();
    }

    private function ownerFilter(array $filters): array
    {
        $where = '1 = 1';
        $params = [];
        $status = (string) ($filters['status'] ?? '');
        $date = (string) ($filters['date'] ?? '');
        $query = trim((string) ($filters['q'] ?? ''));
        if (in_array($status, $this->statuses(), true)) {
            $where .= ' AND b.status = :status';
            $params['status'] = $status;
        }
        if ($date !== '') {
            $this->buildStart($date, '00:00');
            $where .= ' AND b.booking_date = :booking_date';
            $params['booking_date'] = $date;
        }
        if ($query !== '') {
            $where .= ' AND (b.booking_code LIKE :search OR b.pet_name LIKE :search OR u.full_name LIKE :search)';
            $params['search'] = '%' . $query . '%';
        }
        return [$where, $params];
    }

    private function buildStart(string $date, string $time): DateTimeImmutable
    {
        $value = $date . ' ' . $time;
        $dateTime = DateTimeImmutable::createFromFormat('!Y-m-d H:i', $value);
        $errors = DateTimeImmutable::getLastErrors();
        if (!$dateTime || ($errors !== false && ($errors['warning_count'] || $errors['error_count']))
            || $dateTime->format('Y-m-d H:i') !== $value) {
            throw new InvalidArgumentException('Ngày hoặc giờ đặt lịch không hợp lệ.');
        }
        return $dateTime;
    }

    private function generateCode(): string
    {
        return 'SPA-' . date('Ymd') . '-' . strtoupper(bin2hex(random_bytes(3)));
    }

    private function acquireDateLock(string $name): bool
    {
        $stmt = $this->db->prepare('SELECT GET_LOCK(:name, 10)');
        $stmt->execute(['name' => $name]);

        return (int) $stmt->fetchColumn() === 1;
    }

    private function releaseDateLock(string $name): void
    {
        $stmt = $this->db->prepare('SELECT RELEASE_LOCK(:name)');
        $stmt->execute(['name' => $name]);
    }

    private function textLength(string $value): int
    {
        return function_exists('mb_strlen') ? mb_strlen($value) : strlen($value);
    }

    private function statuses(): array
    {
        return ['pending', 'confirmed', 'in_progress', 'completed', 'cancelled'];
    }

    private function notifyStatus(
    array $booking,
    string $status
): void {

    $serviceName =
        trim(
            (string) (
                $booking['service_name_snapshot']
                ?? 'Dịch vụ Spa'
            )
        );


    $petName =
        trim(
            (string) (
                $booking['pet_name']
                ?? 'thú cưng'
            )
        );


    /*
    |--------------------------------------------------------------------------
    | NỘI DUNG THEO TRẠNG THÁI
    |--------------------------------------------------------------------------
    */

    $notifications = [

        'confirmed' => [

            'type' =>
                'booking.confirmed',

            'title' =>
                'Lịch hẹn đã được xác nhận',

            'message' =>
                'Lịch '
                . $serviceName
                . ' cho '
                . $petName
                . ' đã được Spa xác nhận.'

        ],


        'in_progress' => [

            'type' =>
                'booking.in_progress',

            'title' =>
                'Spa đang phục vụ',

            'message' =>
                $serviceName
                . ' cho '
                . $petName
                . ' đang được thực hiện.'

        ],


        'completed' => [

            'type' =>
                'booking.completed',

            'title' =>
                'Dịch vụ đã hoàn tất',

            'message' =>
                $serviceName
                . ' cho '
                . $petName
                . ' đã hoàn thành.'

        ],


        'cancelled' => [

            'type' =>
                'booking.cancelled',

            'title' =>
                'Lịch hẹn đã hủy',

            'message' =>
                'Lịch '
                . $serviceName
                . ' cho '
                . $petName
                . ' đã được hủy.'

        ]

    ];


    /*
    |--------------------------------------------------------------------------
    | Không tạo thông báo nếu status không hỗ trợ
    |--------------------------------------------------------------------------
    */

    if (
        !isset(
            $notifications[$status]
        )
    ) {
        return;
    }


    $notification =
        $notifications[$status];


    /*
    |--------------------------------------------------------------------------
    | TẠO THÔNG BÁO CUSTOMER
    |--------------------------------------------------------------------------
    */

    $this->notifications->create(

        (int) $booking[
            'customer_id'
        ],

        $notification[
            'type'
        ],

        $notification[
            'title'
        ],

        $notification[
            'message'
        ],

        '?url=customer/booking&id='
            . (int) $booking['id']

    );
}

    private function notifyActiveOwners(string $type, string $title, string $message, string $link): void
    {
        foreach ((new User())->getActiveOwnerIds() as $ownerId) {
            $this->notifications->create($ownerId, $type, $title, $message, $link);
        }
    }
}