<?php

namespace App\Services;

use App\Models\GoogleSyncLog;
use Illuminate\Support\Facades\Http;

class BookingBlockGoogleService
{
    public function block(array $roomCodes, string $dateFrom, string $dateTo): array
    {
        return $this->send('blockBookingRooms', $roomCodes, $dateFrom, $dateTo, 'booking_block_created');
    }

    public function unblock(array $roomCodes, string $dateFrom, string $dateTo, string $reason = 'booking_block_removed'): array
    {
        return $this->send('unblockBookingRooms', $roomCodes, $dateFrom, $dateTo, $reason);
    }

    public function ping(): array
    {
        return $this->send('ping', [], now()->toDateString(), now()->toDateString(), 'booking_block_ping');
    }

    private function send(string $action, array $roomCodes, string $dateFrom, string $dateTo, string $reason): array
    {
        $url = trim((string) config('hotel.google_booking_block_script_url', ''));
        $rooms = array_values(array_unique(array_filter(array_map(
            static fn ($code) => trim((string) $code),
            $roomCodes
        ))));

        $payload = [
            'action' => $action,
        ];

        if ($action !== 'ping') {
            $payload['date_from'] = $dateFrom;
            $payload['date_to'] = $dateTo;
            $payload['rooms'] = implode(',', $rooms);
            $payload['label'] = 'Booking';
        }

        $secret = trim((string) config('hotel.google_script_secret', ''));
        if ($secret !== '') {
            // secret_key is the main parameter used by the current Apps Script.
            // The extra aliases make the request tolerant to older/newer script variants.
            $payload['secret_key'] = $secret;
            $payload['hotel_secret_key'] = $secret;
            $payload['secret'] = $secret;
        }

        if ($url === '') {
            $result = [
                'ok' => false,
                'status' => 'error',
                'message' => 'URL Apps Script для блокировки Booking не указан.',
                'action' => $action,
                'reason' => $reason,
                'diagnostics' => $this->diagnostics($url, $payload, $secret),
            ];
            $this->log($payload, $result);

            return $result;
        }

        $maxAttempts = $action === 'ping' ? 1 : 3;
        $result = [
            'ok' => false,
            'status' => 'error',
            'message' => 'Запрос в Google Apps Script не выполнен.',
            'action' => $action,
            'reason' => $reason,
            'attempts' => 0,
            'diagnostics' => $this->diagnostics($url, $payload, $secret),
        ];

        for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
            try {
                // Google Apps Script Web Apps are more reliable here with GET params.
                // POST may be redirected by Google and lose form/json parameters.
                $response = Http::timeout(45)
                    ->acceptJson()
                    ->withHeaders([
                        'User-Agent' => 'Hotel-Pineta-Laravel-Booking-Block/44',
                    ])
                    ->get($url, $payload);

                $rawBody = trim($response->body());
                $data = $response->json();

                if (! is_array($data)) {
                    $data = [
                        'ok' => false,
                        'raw' => $rawBody,
                    ];
                }

                $ok = (bool) ($data['ok'] ?? false);
                $message = (string) (
                    $data['message']
                    ?? $data['error']
                    ?? ($ok ? 'OK' : 'Apps Script вернул ошибку.')
                );

                $result = [
                    'ok' => $ok,
                    'status' => $ok ? 'success' : 'error',
                    'message' => $message,
                    'http_status' => $response->status(),
                    'action' => $action,
                    'reason' => $reason,
                    'attempts' => $attempt,
                    'max_attempts' => $maxAttempts,
                    'rooms' => $rooms,
                    'date_from' => $action === 'ping' ? null : $dateFrom,
                    'date_to' => $action === 'ping' ? null : $dateTo,
                    'response' => $data,
                    'diagnostics' => $this->diagnostics($url, $payload, $secret),
                ];

                if ($ok || ! $this->shouldRetry($response->status(), $message, $attempt, $maxAttempts)) {
                    break;
                }
            } catch (\Throwable $e) {
                $result = [
                    'ok' => false,
                    'status' => 'error',
                    'message' => $e->getMessage(),
                    'action' => $action,
                    'reason' => $reason,
                    'attempts' => $attempt,
                    'max_attempts' => $maxAttempts,
                    'rooms' => $rooms,
                    'date_from' => $action === 'ping' ? null : $dateFrom,
                    'date_to' => $action === 'ping' ? null : $dateTo,
                    'diagnostics' => $this->diagnostics($url, $payload, $secret),
                ];

                if ($attempt >= $maxAttempts) {
                    break;
                }
            }

            usleep(700000);
        }

        $this->log($payload, $result);

        return $result;
    }

    private function log(array $payload, array $result): void
    {
        GoogleSyncLog::create([
            'booking_id' => null,
            'direction' => 'to_google',
            'target' => 'booking_block_script',
            'payload_json' => $this->redactSecret($payload),
            'response_json' => $result,
            'status' => (string) ($result['status'] ?? 'error'),
            'attempts' => (int) ($result['attempts'] ?? 1),
            'synced_at' => in_array(($result['status'] ?? 'error'), ['success', 'skipped'], true) ? now() : null,
        ]);
    }

    private function shouldRetry(int $httpStatus, string $message, int $attempt, int $maxAttempts): bool
    {
        if ($attempt >= $maxAttempts) {
            return false;
        }

        $lowerMessage = mb_strtolower($message);

        if ($httpStatus === 429 || $httpStatus >= 500) {
            return true;
        }

        return str_contains($lowerMessage, 'timeout')
            || str_contains($lowerMessage, 'timed out')
            || str_contains($lowerMessage, 'temporar')
            || str_contains($lowerMessage, 'service unavailable')
            || str_contains($lowerMessage, 'try again');
    }

    private function diagnostics(string $url, array $payload, string $secret): array
    {
        $maskedPayload = $this->redactSecret($payload);

        return [
            'request_url_masked' => $this->buildMaskedUrl($url, $payload),
            'script_url' => $url,
            'action' => (string) ($payload['action'] ?? ''),
            'rooms' => (string) ($payload['rooms'] ?? ''),
            'date_from' => (string) ($payload['date_from'] ?? ''),
            'date_to' => (string) ($payload['date_to'] ?? ''),
            'secret_present' => $secret !== '',
            'secret_length' => strlen($secret),
            'secret_sha256_8' => $secret !== '' ? substr(hash('sha256', $secret), 0, 8) : '',
            'payload_masked' => $maskedPayload,
        ];
    }

    private function buildMaskedUrl(string $url, array $payload): string
    {
        if ($url === '') {
            return '';
        }

        $maskedPayload = $this->redactSecret($payload);
        $query = http_build_query($maskedPayload, '', '&', PHP_QUERY_RFC3986);

        return $url.(str_contains($url, '?') ? '&' : '?').$query;
    }

    private function redactSecret(array $payload): array
    {
        foreach (['secret_key', 'hotel_secret_key', 'secret'] as $key) {
            if (array_key_exists($key, $payload)) {
                $payload[$key] = '***';
            }
        }

        return $payload;
    }
}
