<?php

declare(strict_types=1);

define('BASE_PATH', dirname(__DIR__));
date_default_timezone_set('Asia/Ho_Chi_Minh');

$testDatabase = (string) getenv('PET_SPA_TEST_DB');
if (!preg_match('/^pet_shop_e2e_[a-z0-9_]+$/', $testDatabase)) {
    throw new RuntimeException('Refusing to run: PET_SPA_TEST_DB must be an isolated pet_shop_e2e_* database.');
}

require_once BASE_PATH . '/config/database.php';
require_once BASE_PATH . '/app/models/Booking.php';

$database = new Database();
if ($database->databaseName() !== $testDatabase || $testDatabase === 'pet_shop') {
    throw new RuntimeException('Refusing destructive E2E test against the main database.');
}

$pdo = $database->connect();
$currentDatabase = (string) $pdo->query('SELECT DATABASE()')->fetchColumn();
if ($currentDatabase !== $testDatabase || $currentDatabase === 'pet_shop') {
    throw new RuntimeException('Refusing destructive E2E test against the main database.');
}

$suffix = bin2hex(random_bytes(4));
$pdo->prepare(
    'INSERT INTO users (full_name, phone, email, password_hash, role)
     VALUES (?, ?, ?, ?, "customer")'
)->execute([
    'E2E Customer',
    '09' . random_int(10000000, 99999999),
    'e2e-' . $suffix . '@example.test',
    'x'
]);
$customerId = (int) $pdo->lastInsertId();
$pdo->prepare(
    'INSERT INTO users (full_name, phone, email, password_hash, role)
     VALUES (?, ?, ?, ?, "customer")'
)->execute([
    'E2E Other Customer',
    '03' . random_int(10000000, 99999999),
    'e2e-other-' . $suffix . '@example.test',
    'x'
]);
$otherCustomerId = (int) $pdo->lastInsertId();
$pdo->prepare(
    'INSERT INTO users (full_name, phone, email, password_hash, role)
     VALUES (?, ?, ?, ?, "owner")'
)->execute([
    'E2E Owner',
    '08' . random_int(10000000, 99999999),
    'e2e-owner-' . $suffix . '@example.test',
    'x'
]);
$ownerId = (int) $pdo->lastInsertId();

$pdo->prepare(
    'INSERT INTO service_categories (name, slug, is_active)
     VALUES (?, ?, 1)'
)->execute(['Tắm E2E', 'tam-e2e-' . $suffix]);
$categoryId = (int) $pdo->lastInsertId();

$pdo->prepare(
    'INSERT INTO services (category_id, name, slug, price, duration_minutes, is_active)
     VALUES (?, ?, ?, 150000, 60, 1)'
)->execute([$categoryId, 'Tắm E2E', 'tam-e2e-' . $suffix]);
$serviceId = (int) $pdo->lastInsertId();

$date = (new DateTimeImmutable('next monday'))->format('Y-m-d');
$booking = new Booking();
if ($booking->getAvailableSlots($serviceId, (new DateTimeImmutable('next sunday'))->format('Y-m-d')) !== []) {
    throw new RuntimeException('Closed Sunday exposed available slots.');
}
$pdo->prepare(
    'INSERT INTO blocked_slots (blocked_date, start_time, end_time, reason, created_by)
     VALUES (?, "12:00:00", "13:00:00", "E2E partial block", ?)'
)->execute([$date, $ownerId]);
if (array_intersect(['12:00', '12:30'], $booking->getAvailableSlots($serviceId, $date)) !== []) {
    throw new RuntimeException('Partial blocked slot exposed available time.');
}
$fullBlockDate = (new DateTimeImmutable('next tuesday'))->format('Y-m-d');
$pdo->prepare(
    'INSERT INTO blocked_slots (blocked_date, start_time, end_time, reason, created_by)
     VALUES (?, NULL, NULL, "E2E full-day block", ?)'
)->execute([$fullBlockDate, $ownerId]);
if ($booking->getAvailableSlots($serviceId, $fullBlockDate) !== []) {
    throw new RuntimeException('Full-day block exposed available time.');
}
$first = $booking->createForCustomer($customerId, [
    'service_id' => $serviceId,
    'booking_date' => $date,
    'start_time' => '09:00',
    'pet_name' => 'Milo',
    'pet_type' => 'dog',
    'pet_weight' => '4.2'
]);

if (in_array('09:00', $booking->getAvailableSlots($serviceId, $date), true)) {
    throw new RuntimeException('Booked time remained available.');
}

try {
    $booking->createForCustomer($customerId, [
        'service_id' => $serviceId,
        'booking_date' => $date,
        'start_time' => '09:30',
        'pet_name' => 'Misa',
        'pet_type' => 'cat'
    ]);
    throw new RuntimeException('Overlapping booking was accepted.');
} catch (InvalidArgumentException) {
    // Expected: the active booking owns this overlapping interval.
}

$booking->cancelByCustomer((int) $first['id'], $customerId);
$second = $booking->createForCustomer($customerId, [
    'service_id' => $serviceId,
    'booking_date' => $date,
    'start_time' => '10:00',
    'pet_name' => 'Milo',
    'pet_type' => 'dog'
]);
if ($booking->findForCustomer((int) $second['id'], $otherCustomerId) !== null) {
    throw new RuntimeException('Customer booking ownership leaked.');
}
try {
    $booking->cancelByCustomer((int) $second['id'], $otherCustomerId);
    throw new RuntimeException('Customer cancelled another customer booking.');
} catch (InvalidArgumentException) {
    // Expected ownership rejection.
}
$pdo->prepare('UPDATE services SET price = 200000, duration_minutes = 90 WHERE id = ?')->execute([$serviceId]);
try {
    $booking->updateStatus((int) $second['id'], $ownerId, 'completed');
    throw new RuntimeException('Invalid owner status transition was accepted.');
} catch (InvalidArgumentException) {
    // Expected server-side transition rejection.
}
$booking->updateStatus((int) $second['id'], $ownerId, 'confirmed', 'Đã xác nhận');
$booking->updateStatus((int) $second['id'], $ownerId, 'in_progress');
$finished = $booking->updateStatus((int) $second['id'], $ownerId, 'completed');

if ($finished['status'] !== 'completed'
    || (int) $finished['duration_minutes_snapshot'] !== 60
    || (float) $finished['service_price_snapshot'] !== 150000.0) {
    throw new RuntimeException('Status flow or booking snapshots failed.');
}

$logs = (int) $pdo->query(
    'SELECT COUNT(*) FROM booking_status_logs WHERE booking_id = ' . (int) $second['id']
)->fetchColumn();
$notifications = (int) $pdo->query(
    'SELECT COUNT(*) FROM notifications WHERE user_id = ' . $customerId
)->fetchColumn();
if ($logs !== 4 || $notifications < 4) {
    throw new RuntimeException('Audit trail or notifications are incomplete.');
}

echo "SPA E2E PASS: guard, hours, blocked slots, conflict, ownership, cancellation, snapshots, status flow, logs, notifications\n";
