<?php
/**
 * channel_webhook.php
 * Place: /rooms/modules/channel_manager/channel_webhook.php
 *
 * Public webhook endpoint for channel providers / channel manager.
 *
 * Example endpoint:
 * https://yourdomain.com/rooms/modules/channel_manager/channel_webhook.php?connection_id=1
 *
 * Optional provider:
 * https://yourdomain.com/rooms/modules/channel_manager/channel_webhook.php?provider=nightsbridge
 *
 * Security:
 * - Uses channel_connections.webhook_secret_enc
 * - Accepts secret via:
 *   1. X-Webhook-Secret header
 *   2. X-Channel-Secret header
 *   3. ?secret=...
 *
 * Payload:
 * - Accepts JSON POST.
 * - Normalizes booking fields into channel_booking_imports.
 */

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

header('Content-Type: application/json; charset=utf-8');

if (!function_exists('wh_json_out')) {
    function wh_json_out(array $payload, int $statusCode = 200): void {
        http_response_code($statusCode);
        echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        exit;
    }
}

if (!function_exists('wh_table_exists')) {
    function wh_table_exists(PDO $pdo, string $tableName): bool {
        $stmt = $pdo->prepare("
            SELECT COUNT(*)
            FROM information_schema.tables
            WHERE table_schema = DATABASE()
              AND table_name = :table_name
        ");
        $stmt->execute([':table_name' => $tableName]);
        return (int)$stmt->fetchColumn() > 0;
    }
}

if (!function_exists('wh_column_exists')) {
    function wh_column_exists(PDO $pdo, string $tableName, string $columnName): bool {
        $stmt = $pdo->prepare("
            SELECT COUNT(*)
            FROM information_schema.columns
            WHERE table_schema = DATABASE()
              AND table_name = :table_name
              AND column_name = :column_name
        ");
        $stmt->execute([
            ':table_name' => $tableName,
            ':column_name' => $columnName,
        ]);
        return (int)$stmt->fetchColumn() > 0;
    }
}

if (!function_exists('wh_decrypt_secret')) {
    function wh_decrypt_secret(?string $value): string {
        $value = (string)$value;
        if ($value === '') return '';
        $decoded = base64_decode($value, true);
        return $decoded === false ? $value : $decoded;
    }
}

if (!function_exists('wh_header')) {
    function wh_header(string $name): string {
        $serverKey = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
        return trim((string)($_SERVER[$serverKey] ?? ''));
    }
}

if (!function_exists('wh_get_input_secret')) {
    function wh_get_input_secret(): string {
        $secret = wh_header('X-Webhook-Secret');
        if ($secret !== '') return $secret;

        $secret = wh_header('X-Channel-Secret');
        if ($secret !== '') return $secret;

        $secret = wh_header('X-Signature');
        if ($secret !== '') return $secret;

        return trim((string)($_GET['secret'] ?? ''));
    }
}

if (!function_exists('wh_first')) {
    function wh_first(array $data, array $keys, $default = '') {
        foreach ($keys as $key) {
            if (isset($data[$key]) && trim((string)$data[$key]) !== '') {
                return $data[$key];
            }
        }
        return $default;
    }
}

if (!function_exists('wh_create_tables')) {
    function wh_create_tables(PDO $pdo): void {
        $pdo->exec("
            CREATE TABLE IF NOT EXISTS channel_booking_imports (
                id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
                connection_id INT UNSIGNED NOT NULL,
                provider VARCHAR(50) NOT NULL,
                external_booking_id VARCHAR(150) NOT NULL,
                order_id INT UNSIGNED NULL,
                guest_name VARCHAR(180) NULL,
                check_in DATE NULL,
                check_out DATE NULL,
                total_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
                currency VARCHAR(10) NULL,
                import_status VARCHAR(40) NOT NULL DEFAULT 'pending',
                raw_payload MEDIUMTEXT NULL,
                imported_at DATETIME NULL,
                created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                UNIQUE KEY uq_channel_booking (provider, external_booking_id),
                INDEX idx_channel_booking_connection (connection_id),
                INDEX idx_channel_booking_order (order_id),
                INDEX idx_channel_booking_status (import_status)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ");

        $pdo->exec("
            CREATE TABLE IF NOT EXISTS channel_sync_log (
                id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
                connection_id INT UNSIGNED NULL,
                provider VARCHAR(50) NOT NULL,
                sync_type VARCHAR(80) NOT NULL,
                sync_direction VARCHAR(30) NOT NULL,
                status VARCHAR(30) NOT NULL,
                request_payload MEDIUMTEXT NULL,
                response_payload MEDIUMTEXT NULL,
                message TEXT NULL,
                started_at DATETIME NOT NULL,
                completed_at DATETIME NULL,
                created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_channel_log_connection (connection_id),
                INDEX idx_channel_log_provider (provider),
                INDEX idx_channel_log_status (status),
                INDEX idx_channel_log_started (started_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ");
    }
}

if (!function_exists('wh_log')) {
    function wh_log(PDO $pdo, ?int $connectionId, string $provider, string $status, array $requestPayload, array $responsePayload, string $message): int {
        $stmt = $pdo->prepare("
            INSERT INTO channel_sync_log
            (connection_id, provider, sync_type, sync_direction, status, request_payload, response_payload, message, started_at, completed_at)
            VALUES
            (:connection_id, :provider, 'webhook_booking', 'webhook', :status, :request_payload, :response_payload, :message, NOW(), NOW())
        ");
        $stmt->execute([
            ':connection_id' => $connectionId ?: null,
            ':provider' => $provider,
            ':status' => $status,
            ':request_payload' => json_encode($requestPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
            ':response_payload' => json_encode($responsePayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
            ':message' => $message,
        ]);

        return (int)$pdo->lastInsertId();
    }
}

if (!function_exists('wh_get_connection')) {
    function wh_get_connection(PDO $pdo): ?array {
        $connectionId = (int)($_GET['connection_id'] ?? 0);
        $provider = trim((string)($_GET['provider'] ?? ''));

        if ($connectionId > 0) {
            $stmt = $pdo->prepare("
                SELECT *
                FROM channel_connections
                WHERE id = :id
                  AND is_active = 1
                LIMIT 1
            ");
            $stmt->execute([':id' => $connectionId]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            return $row ?: null;
        }

        if ($provider !== '') {
            $stmt = $pdo->prepare("
                SELECT *
                FROM channel_connections
                WHERE provider = :provider
                  AND is_active = 1
                ORDER BY id ASC
                LIMIT 1
            ");
            $stmt->execute([':provider' => $provider]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            return $row ?: null;
        }

        return null;
    }
}

if (!function_exists('wh_normalize_booking')) {
    function wh_normalize_booking(array $payload, array $connection): array {
        /*
         * Handles common payload shapes:
         * 1. Single booking at root.
         * 2. {"booking": {...}}
         * 3. {"reservation": {...}}
         * 4. {"data": {...}}
         */

        $booking = $payload['booking'] ?? $payload['reservation'] ?? $payload['data'] ?? $payload;
        if (!is_array($booking)) $booking = $payload;

        $guest = $booking['guest'] ?? $booking['customer'] ?? [];
        if (!is_array($guest)) $guest = [];

        $rooms = $booking['rooms'] ?? $booking['room'] ?? [];
        $firstRoom = [];
        if (is_array($rooms)) {
            if (isset($rooms[0]) && is_array($rooms[0])) {
                $firstRoom = $rooms[0];
            } elseif (isset($rooms['id']) || isset($rooms['room_id']) || isset($rooms['external_room_id'])) {
                $firstRoom = $rooms;
            }
        }

        $externalBookingId = (string)wh_first($booking, [
            'external_booking_id', 'booking_id', 'reservation_id', 'id', 'confirmation_number', 'reference'
        ], '');

        if ($externalBookingId === '') {
            $externalBookingId = strtoupper((string)($connection['provider'] ?? 'OTA')) . '-' . date('YmdHis') . '-' . substr(md5(json_encode($payload)), 0, 8);
        }

        $guestName = trim((string)wh_first($booking, ['guest_name', 'customer_name', 'name'], ''));
        if ($guestName === '') {
            $first = (string)wh_first($guest, ['first_name', 'firstname', 'firstName'], '');
            $last = (string)wh_first($guest, ['last_name', 'lastname', 'lastName'], '');
            $guestName = trim($first . ' ' . $last);
        }
        if ($guestName === '') $guestName = 'OTA Guest';

        $checkIn = (string)wh_first($booking, ['check_in', 'checkin', 'arrival_date', 'arrival', 'start_date'], '');
        $checkOut = (string)wh_first($booking, ['check_out', 'checkout', 'departure_date', 'departure', 'end_date'], '');

        $roomExternalId = (string)wh_first($booking, ['room_type_external_id', 'external_room_id', 'room_id'], '');
        if ($roomExternalId === '') {
            $roomExternalId = (string)wh_first($firstRoom, ['room_type_external_id', 'external_room_id', 'room_id', 'id'], '');
        }

        $rateExternalId = (string)wh_first($booking, ['rate_external_id', 'external_rate_id', 'rate_id'], '');
        if ($rateExternalId === '') {
            $rateExternalId = (string)wh_first($firstRoom, ['rate_external_id', 'external_rate_id', 'rate_id'], '');
        }

        $totalAmount = (float)wh_first($booking, ['total_amount', 'amount', 'total', 'price', 'grand_total'], 0);
        $currency = (string)wh_first($booking, ['currency', 'currency_code'], 'USD');

        return [
            'external_booking_id' => $externalBookingId,
            'external_property_id' => (string)wh_first($booking, ['external_property_id', 'hotel_id', 'property_id'], (string)($connection['external_property_id'] ?? '')),
            'guest_name' => $guestName,
            'guest_email' => (string)wh_first($booking, ['guest_email', 'email'], (string)wh_first($guest, ['email'], '')),
            'guest_phone' => (string)wh_first($booking, ['guest_phone', 'phone', 'mobile'], (string)wh_first($guest, ['phone', 'mobile'], '')),
            'check_in' => $checkIn,
            'check_out' => $checkOut,
            'room_type_external_id' => $roomExternalId,
            'rate_external_id' => $rateExternalId,
            'rooms' => (int)wh_first($booking, ['rooms_count', 'room_count', 'rooms'], 1),
            'adults' => (int)wh_first($booking, ['adults', 'adult_count'], 1),
            'children' => (int)wh_first($booking, ['children', 'child_count'], 0),
            'total_amount' => $totalAmount,
            'currency' => $currency,
            'status' => (string)wh_first($booking, ['status', 'reservation_status'], 'confirmed'),
            'special_requests' => (string)wh_first($booking, ['special_requests', 'notes', 'remarks'], ''),
            'raw_payload' => $payload,
        ];
    }
}

try {
    wh_create_tables($pdo);

    if (!wh_table_exists($pdo, 'channel_connections')) {
        wh_json_out([
            'ok' => false,
            'error' => 'channel_connections table does not exist. Create a channel connection first.',
        ], 500);
    }

    if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
        wh_json_out([
            'ok' => false,
            'error' => 'Webhook expects POST JSON.',
            'hint' => 'POST to this endpoint with connection_id or provider.',
        ], 405);
    }

    $connection = wh_get_connection($pdo);
    if (!$connection) {
        wh_json_out([
            'ok' => false,
            'error' => 'No active channel connection found. Supply ?connection_id=ID or ?provider=nightsbridge.',
        ], 404);
    }

    $provider = (string)$connection['provider'];
    $connectionId = (int)$connection['id'];

    $expectedSecret = wh_decrypt_secret($connection['webhook_secret_enc'] ?? '');
    if ($expectedSecret !== '') {
        $incomingSecret = wh_get_input_secret();

        if ($incomingSecret === '' || !hash_equals($expectedSecret, $incomingSecret)) {
            wh_log($pdo, $connectionId, $provider, 'failed', [
                'headers_checked' => ['X-Webhook-Secret', 'X-Channel-Secret', 'X-Signature', 'secret query param'],
            ], [
                'error' => 'Invalid webhook secret.',
            ], 'Webhook rejected: invalid secret.');

            wh_json_out([
                'ok' => false,
                'error' => 'Invalid webhook secret.',
            ], 401);
        }
    }

    $rawBody = file_get_contents('php://input');
    $payload = json_decode((string)$rawBody, true);

    if (!is_array($payload)) {
        wh_log($pdo, $connectionId, $provider, 'failed', [
            'raw_body' => $rawBody,
        ], [
            'error' => 'Invalid JSON payload.',
        ], 'Webhook rejected: invalid JSON payload.');

        wh_json_out([
            'ok' => false,
            'error' => 'Invalid JSON payload.',
        ], 400);
    }

    /*
     * Support batch payload:
     * {"bookings":[...]}
     */
    $incomingBookings = [];
    if (isset($payload['bookings']) && is_array($payload['bookings'])) {
        $incomingBookings = $payload['bookings'];
    } elseif (isset($payload['reservations']) && is_array($payload['reservations'])) {
        $incomingBookings = $payload['reservations'];
    } else {
        $incomingBookings = [$payload];
    }

    $results = [];
    $imported = 0;
    $duplicates = 0;
    $failed = 0;

    foreach ($incomingBookings as $incomingBooking) {
        if (!is_array($incomingBooking)) {
            $failed++;
            $results[] = [
                'ok' => false,
                'error' => 'Booking item is not an object.',
            ];
            continue;
        }

        $normalized = wh_normalize_booking($incomingBooking, $connection);
        $externalBookingId = trim((string)$normalized['external_booking_id']);

        if ($externalBookingId === '') {
            $failed++;
            $results[] = [
                'ok' => false,
                'error' => 'Missing external booking ID.',
            ];
            continue;
        }

        $stmt = $pdo->prepare("
            SELECT id, import_status, order_id
            FROM channel_booking_imports
            WHERE provider = :provider
              AND external_booking_id = :external_booking_id
            LIMIT 1
        ");
        $stmt->execute([
            ':provider' => $provider,
            ':external_booking_id' => $externalBookingId,
        ]);
        $existing = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($existing) {
            $duplicates++;
            $results[] = [
                'ok' => true,
                'status' => 'duplicate',
                'external_booking_id' => $externalBookingId,
                'import_id' => (int)$existing['id'],
                'order_id' => (int)($existing['order_id'] ?? 0),
            ];
            continue;
        }

        $stmt = $pdo->prepare("
            INSERT INTO channel_booking_imports
            (
                connection_id, provider, external_booking_id, order_id,
                guest_name, check_in, check_out, total_amount, currency,
                import_status, raw_payload, imported_at
            )
            VALUES
            (
                :connection_id, :provider, :external_booking_id, NULL,
                :guest_name, :check_in, :check_out, :total_amount, :currency,
                'pending', :raw_payload, NOW()
            )
        ");
        $stmt->execute([
            ':connection_id' => $connectionId,
            ':provider' => $provider,
            ':external_booking_id' => $externalBookingId,
            ':guest_name' => $normalized['guest_name'],
            ':check_in' => $normalized['check_in'] !== '' ? $normalized['check_in'] : null,
            ':check_out' => $normalized['check_out'] !== '' ? $normalized['check_out'] : null,
            ':total_amount' => (float)$normalized['total_amount'],
            ':currency' => $normalized['currency'],
            ':raw_payload' => json_encode($normalized, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
        ]);

        $importId = (int)$pdo->lastInsertId();
        $imported++;

        $results[] = [
            'ok' => true,
            'status' => 'imported',
            'external_booking_id' => $externalBookingId,
            'import_id' => $importId,
        ];
    }

    $status = $failed > 0 ? 'warning' : 'success';

    $logId = wh_log($pdo, $connectionId, $provider, $status, $payload, [
        'imported' => $imported,
        'duplicates' => $duplicates,
        'failed' => $failed,
        'results' => $results,
    ], 'Webhook processed. Imported: ' . $imported . ', duplicates: ' . $duplicates . ', failed: ' . $failed . '.');

    wh_json_out([
        'ok' => $failed === 0,
        'connection_id' => $connectionId,
        'provider' => $provider,
        'imported' => $imported,
        'duplicates' => $duplicates,
        'failed' => $failed,
        'log_id' => $logId,
        'results' => $results,
    ], $failed === 0 ? 200 : 207);

} catch (Throwable $e) {
    try {
        if (isset($pdo) && $pdo instanceof PDO && wh_table_exists($pdo, 'channel_sync_log')) {
            wh_log($pdo, isset($connectionId) ? (int)$connectionId : null, isset($provider) ? (string)$provider : 'unknown', 'failed', [
                'exception' => $e->getMessage(),
            ], [], 'Webhook exception: ' . $e->getMessage());
        }
    } catch (Throwable $ignored) {}

    wh_json_out([
        'ok' => false,
        'error' => $e->getMessage(),
    ], 500);
}
