<?php

namespace App\Services;

use App\Models\DailyRoomRate;
use App\Models\RateImportLog;
use App\Models\Room;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

class RateImportService
{
    public function importGuestCalendar(string $mode = 'manual', ?int $triggeredByUserId = null, ?string $dateFrom = null, ?string $dateTo = null): RateImportLog
    {
        $timezone = (string) config('app.timezone', 'Europe/Rome');
        $from = $dateFrom ? Carbon::parse($dateFrom, $timezone)->startOfDay() : Carbon::today($timezone);
        $to = $dateTo ? Carbon::parse($dateTo, $timezone)->startOfDay() : Carbon::parse('2026-11-01', $timezone)->startOfDay();

        if ($to->lt($from)) {
            $to = $from->copy();
        }

        $rooms = Room::query()
            ->where('is_active', true)
            ->orderBy('sort_order')
            ->get(['id', 'code', 'title']);

        $daysProcessed = $from->diffInDays($to) + 1;
        $rowsWritten = 0;

        $log = RateImportLog::create([
            'source' => 'guest_google_sheet_import',
            'triggered_by_user_id' => $triggeredByUserId,
            'mode' => $mode,
            'status' => 'running',
            'date_from' => $from->toDateString(),
            'date_to' => $to->toDateString(),
            'rooms_processed' => $rooms->count(),
            'days_processed' => $daysProcessed,
            'rows_written' => 0,
            'message' => 'Импорт тарифов из гостевой Google-таблицы запущен.',
            'details_json' => [
                'note' => 'Диапазон импорта: с текущего дня до 01.11.2026. Быстрый getGuestRatesRange используется, если доступен; иначе включается запасной getGuestPricing.',
                'guest_sheet_name' => config('hotel.google_guest_sheet_name'),
                'guest_sheet_url' => config('hotel.google_guest_sheet_url'),
                'guest_script_url' => config('hotel.google_guest_script_url'),
                'range_mode' => 'today_to_2026_11_01',
                'timezone' => $timezone,
            ],
        ]);

        $bridge = app(GuestGoogleBridgeService::class);
        $roomCodeMap = $rooms->mapWithKeys(fn (Room $room) => [$this->normalizeRoomCode((string) $room->code) => $room])->all();

        try {
            [$days, $fetchMethod, $remoteFailures] = $this->fetchRemoteDays($bridge, $rooms, $from, $to);

            if ($days === []) {
                throw new \RuntimeException('Гостевой Web App не вернул ни одного дня с тарифами для выбранного периода.');
            }

            $rows = [];
            $failures = [];
            $importedDates = [];

            foreach ($days as $dayRow) {
                if (! is_array($dayRow)) {
                    continue;
                }

                $date = (string) ($dayRow['date'] ?? '');
                if ($date === '') {
                    continue;
                }

                $ratesByRoom = $dayRow['rates'] ?? [];
                if (! is_array($ratesByRoom) || $ratesByRoom === []) {
                    $failures[] = ['date' => $date, 'reason' => 'empty_or_missing_rates_map'];
                    continue;
                }

                $dateHasValidRate = false;

                foreach ($ratesByRoom as $roomCode => $rawRates) {
                    $normalizedCode = $this->normalizeRoomCode((string) $roomCode);
                    $room = $roomCodeMap[$normalizedCode] ?? null;
                    if (! $room) {
                        continue;
                    }

                    $pricing = $this->normalizeRemotePricing(['rates' => $rawRates]);
                    if (! $pricing) {
                        $failures[] = ['room_code' => $roomCode, 'date' => $date, 'reason' => 'empty_payload'];
                        continue;
                    }

                    $dateHasValidRate = true;
                    $rows[] = [
                        'room_id' => $room->id,
                        'rate_date' => $date,
                        'price_breakfast' => $pricing['price_breakfast'],
                        'price_no_breakfast' => $pricing['price_no_breakfast'],
                        'price_breakfast_3plus' => $pricing['price_breakfast_3plus'],
                        'price_no_breakfast_3plus' => $pricing['price_no_breakfast_3plus'],
                        'price_breakfast_discount' => $pricing['price_breakfast_discount'],
                        'price_no_breakfast_discount' => $pricing['price_no_breakfast_discount'],
                        'price_breakfast_discount_3plus' => $pricing['price_breakfast_discount_3plus'],
                        'price_no_breakfast_discount_3plus' => $pricing['price_no_breakfast_discount_3plus'],
                        'min_nights' => $pricing['min_nights'],
                        'is_closed' => $pricing['is_closed'],
                        'notes' => 'Imported from guest Google sheet Web App',
                        'created_at' => now(),
                        'updated_at' => now(),
                    ];
                }

                if ($dateHasValidRate) {
                    $importedDates[$date] = true;
                }
            }

            if ($rows === []) {
                throw new \RuntimeException('Не удалось получить ни одной валидной записи тарифа из гостевой Google-таблицы. Пустые строки были пропущены.');
            }

            DB::transaction(function () use ($rows, $from, $to): void {
                DailyRoomRate::query()
                    ->whereDate('rate_date', '<', $from->toDateString())
                    ->delete();

                DailyRoomRate::query()
                    ->whereDate('rate_date', '>', $to->toDateString())
                    ->delete();

                DailyRoomRate::query()
                    ->whereBetween('rate_date', [$from->toDateString(), $to->toDateString()])
                    ->delete();

                foreach (array_chunk($rows, 500) as $chunk) {
                    DailyRoomRate::query()->insert($chunk);
                }
            });

            $rowsWritten = count($rows);

            $missingDates = [];
            $cursor = $from->copy();
            while ($cursor->lte($to)) {
                $day = $cursor->toDateString();
                if (! isset($importedDates[$day])) {
                    $missingDates[] = $day;
                }
                $cursor->addDay();
            }

            $status = (empty($failures) && empty($missingDates) && empty($remoteFailures)) ? 'success' : 'partial';
            $message = $status === 'success'
                ? 'Импорт тарифов из гостевой Google-таблицы завершён успешно.'
                : 'Импорт тарифов завершён частично: заполненные тарифы сохранены, пустые дни/комнаты пропущены.';

            $log->update([
                'status' => $status,
                'rows_written' => $rowsWritten,
                'message' => $message,
                'details_json' => array_merge($log->details_json ?? [], [
                    'fetch_method' => $fetchMethod,
                    'days_returned' => count($days),
                    'dates_with_valid_rates' => count($importedDates),
                    'missing_or_empty_dates_preview' => array_slice($missingDates, 0, 80),
                    'remote_failures_count' => count($remoteFailures),
                    'remote_failures_preview' => array_slice($remoteFailures, 0, 40),
                    'failures_count' => count($failures),
                    'failures_preview' => array_slice($failures, 0, 25),
                ]),
                'completed_at' => now(),
            ]);
        } catch (\Throwable $e) {
            $log->update([
                'status' => 'error',
                'rows_written' => $rowsWritten,
                'message' => 'Ошибка импорта тарифов: '.$e->getMessage(),
                'details_json' => array_merge($log->details_json ?? [], [
                    'exception' => $e->getMessage(),
                ]),
                'completed_at' => now(),
            ]);

            throw $e;
        }

        return $log->fresh();
    }

    private function fetchRemoteDays(GuestGoogleBridgeService $bridge, $rooms, Carbon $from, Carbon $to): array
    {
        $rangePayload = $bridge->fetchRatesRange($from->toDateString(), $to->toDateString(), 180);
        $rangeError = null;

        if (is_array($rangePayload) && ($rangePayload['ok'] ?? false) === true) {
            $days = $rangePayload['days'] ?? [];
            if (is_array($days) && $days !== []) {
                return [$days, 'fast_range_getGuestRatesRange', []];
            }

            $rangeError = 'getGuestRatesRange вернул пустой список дней.';
        } else {
            $rangeError = is_array($rangePayload)
                ? (string) ($rangePayload['error'] ?? 'Некорректный ответ getGuestRatesRange.')
                : 'getGuestRatesRange не вернул корректный JSON.';
        }

        $days = [];
        $remoteFailures = [];
        if ($rangeError !== null) {
            $remoteFailures[] = [
                'method' => 'getGuestRatesRange',
                'date_from' => $from->toDateString(),
                'date_to' => $to->toDateString(),
                'reason' => $rangeError,
            ];
        }

        $roomCodes = $rooms->pluck('code')->map(fn ($code) => (string) $code)->values()->all();
        $cursor = $from->copy();

        while ($cursor->lte($to)) {
            $date = $cursor->toDateString();
            $batch = $bridge->fetchPricingBatch($date, $roomCodes);
            $ratesByRoom = [];

            foreach ($roomCodes as $roomCode) {
                $payload = $batch[$roomCode] ?? null;
                if (! is_array($payload)) {
                    $remoteFailures[] = ['method' => 'getGuestPricing', 'date' => $date, 'room_code' => $roomCode, 'reason' => 'empty_response'];
                    continue;
                }

                if (($payload['ok'] ?? false) !== true) {
                    $remoteFailures[] = ['method' => 'getGuestPricing', 'date' => $date, 'room_code' => $roomCode, 'reason' => (string) ($payload['error'] ?? 'ok_false')];
                    continue;
                }

                $rawRates = $payload['rates'] ?? $payload['pricing'] ?? null;
                if (! is_array($rawRates)) {
                    $remoteFailures[] = ['method' => 'getGuestPricing', 'date' => $date, 'room_code' => $roomCode, 'reason' => 'missing_rates'];
                    continue;
                }

                $ratesByRoom[$roomCode] = $rawRates;
            }

            if ($ratesByRoom !== []) {
                $days[] = [
                    'date' => $date,
                    'rates' => $ratesByRoom,
                ];
            }

            $cursor->addDay();
        }

        return [$days, 'fallback_daily_getGuestPricing', $remoteFailures];
    }

    private function normalizeRemotePricing(?array $payload): ?array
    {
        if (! is_array($payload)) {
            return null;
        }

        $raw = $payload['pricing'] ?? $payload['rates'] ?? $payload;
        if (! is_array($raw)) {
            return null;
        }

        $priceBreakfast = $this->pickNumber($raw, ['with_breakfast', 'breakfast', 'price_breakfast']);
        $priceNoBreakfast = $this->pickNumber($raw, ['without_breakfast', 'no_breakfast', 'price_no_breakfast']);
        $priceBreakfast3 = $this->pickNumber($raw, ['with_breakfast_3plus', 'breakfast_long', 'price_breakfast_3plus']);
        $priceNoBreakfast3 = $this->pickNumber($raw, ['without_breakfast_3plus', 'no_breakfast_long', 'price_no_breakfast_3plus']);
        $priceBreakfastDiscount = $this->pickNumber($raw, ['with_breakfast_discount', 'breakfast_discount', 'price_breakfast_discount']);
        $priceNoBreakfastDiscount = $this->pickNumber($raw, ['without_breakfast_discount', 'no_breakfast_discount', 'price_no_breakfast_discount']);
        $priceBreakfastDiscount3 = $this->pickNumber($raw, ['with_breakfast_discount_3plus', 'breakfast_discount_long', 'price_breakfast_discount_3plus']);
        $priceNoBreakfastDiscount3 = $this->pickNumber($raw, ['without_breakfast_discount_3plus', 'no_breakfast_discount_long', 'price_no_breakfast_discount_3plus']);
        $minNights = (int) ($raw['min_nights'] ?? $raw['min_nights_required'] ?? 1);
        $isClosed = (bool) ($raw['is_closed'] ?? false);

        if (($priceBreakfast === null && $priceNoBreakfast === null) || ((float) ($priceBreakfast ?? 0) <= 0 && (float) ($priceNoBreakfast ?? 0) <= 0)) {
            return null;
        }

        return [
            'price_breakfast' => round((float) ($priceBreakfast ?? 0), 2),
            'price_no_breakfast' => round((float) ($priceNoBreakfast ?? 0), 2),
            'price_breakfast_3plus' => round((float) ($priceBreakfast3 ?? $priceBreakfast ?? 0), 2),
            'price_no_breakfast_3plus' => round((float) ($priceNoBreakfast3 ?? $priceNoBreakfast ?? 0), 2),
            'price_breakfast_discount' => round((float) ($priceBreakfastDiscount ?? $priceBreakfast ?? 0), 2),
            'price_no_breakfast_discount' => round((float) ($priceNoBreakfastDiscount ?? $priceNoBreakfast ?? 0), 2),
            'price_breakfast_discount_3plus' => round((float) ($priceBreakfastDiscount3 ?? $priceBreakfastDiscount ?? $priceBreakfast3 ?? $priceBreakfast ?? 0), 2),
            'price_no_breakfast_discount_3plus' => round((float) ($priceNoBreakfastDiscount3 ?? $priceNoBreakfastDiscount ?? $priceNoBreakfast3 ?? $priceNoBreakfast ?? 0), 2),
            'min_nights' => max(1, $minNights),
            'is_closed' => $isClosed,
        ];
    }

    private function pickNumber(array $source, array $keys): ?float
    {
        foreach ($keys as $key) {
            if (isset($source[$key]) && is_numeric($source[$key])) {
                return (float) $source[$key];
            }
        }

        return null;
    }

    private function normalizeRoomCode(string $value): string
    {
        return str_replace(['A', 'a', ' '], ['А', 'а', ''], trim($value));
    }
}
