<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Booking;
use App\Models\PoliceGuestRegistration;
use App\Models\Room;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class TelegramGuestRegistrationController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        $this->authorizeBot($request);
        $this->mergeItalianAliases($request);

        $validated = $request->validate([
            'data_arrivo' => ['required', 'date'],
            'numero_camera' => ['required', 'string', 'max:20'],
            'ospite_numero' => ['nullable', 'integer', 'min:1', 'max:3'],
            'tipo_documento' => ['nullable', 'string', 'max:100'],
            'cognome' => ['required', 'string', 'max:120'],
            'nome' => ['required', 'string', 'max:120'],
            'nazionalita' => ['nullable', 'string', 'max:120'],
            'codice_fiscale' => ['nullable', 'string', 'max:50'],
            'data_nascita' => ['nullable', 'string', 'max:30'],
            'luogo_nascita' => ['nullable', 'string', 'max:160'],
            'sesso' => ['nullable', 'string', 'max:20'],
            'altezza' => ['nullable', 'string', 'max:20'],
            'numero_documento' => ['nullable', 'string', 'max:120'],
            'data_rilascio' => ['nullable', 'string', 'max:30'],
            'data_scadenza' => ['nullable', 'string', 'max:30'],
            'comune_emissione' => ['nullable', 'string', 'max:160'],
            'telegram_chat_id' => ['nullable', 'string', 'max:120'],
            'telegram_message_id' => ['nullable', 'string', 'max:120'],
            'documento_foto_1' => ['nullable', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:10240'],
            'documento_foto_2' => ['nullable', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:10240'],
            'document_front' => ['nullable', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:10240'],
            'document_back' => ['nullable', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:10240'],
            'document_photo_1' => ['nullable', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:10240'],
            'document_photo_2' => ['nullable', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:10240'],
        ]);

        $arrivalDate = Carbon::parse($validated['data_arrivo'])->startOfDay();
        $room = $this->findRoomByCode((string) $validated['numero_camera']);
        $roomCode = $this->normalizeRoomCode((string) ($room?->code ?: $validated['numero_camera']));
        $booking = $room ? $this->findActiveBooking($room->id, $arrivalDate) : null;
        $folder = $this->ensureRegistrationFolder($arrivalDate, $roomCode);
        $safeGuest = $this->safeFilePart(($validated['cognome'] ?? 'ospite').'-'.($validated['nome'] ?? ''));
        $timestamp = now()->format('YmdHis');

        $photo1Path = $this->storeIncomingFile($request, ['documento_foto_1', 'document_front', 'document_photo_1'], $folder, 'documento-ospite-'.$safeGuest.'-01-'.$timestamp);
        $photo2Path = $this->storeIncomingFile($request, ['documento_foto_2', 'document_back', 'document_photo_2'], $folder, 'documento-ospite-'.$safeGuest.'-02-'.$timestamp);

        $registration = PoliceGuestRegistration::create([
            'booking_id' => $booking?->id,
            'room_id' => $room?->id,
            'arrival_date' => $arrivalDate->toDateString(),
            'room_code' => $roomCode,
            'guest_number' => (int) ($validated['ospite_numero'] ?? 1),
            'document_type' => $validated['tipo_documento'] ?? null,
            'surname' => trim((string) $validated['cognome']),
            'name' => trim((string) $validated['nome']),
            'nationality' => trim((string) ($validated['nazionalita'] ?? '')) ?: null,
            'fiscal_code' => trim((string) ($validated['codice_fiscale'] ?? '')) ?: null,
            'birth_date' => $this->parseOptionalDate($validated['data_nascita'] ?? null),
            'birth_place' => trim((string) ($validated['luogo_nascita'] ?? '')) ?: null,
            'gender' => $this->normalizeGender($validated['sesso'] ?? null),
            'height_cm' => $this->normalizeHeight($validated['altezza'] ?? null),
            'document_number' => trim((string) ($validated['numero_documento'] ?? '')) ?: null,
            'document_issue_date' => $this->parseOptionalDate($validated['data_rilascio'] ?? null),
            'document_expiry_date' => $this->parseOptionalDate($validated['data_scadenza'] ?? null),
            'document_issuing_municipality' => trim((string) ($validated['comune_emissione'] ?? '')) ?: null,
            'document_photo_1_path' => $photo1Path,
            'document_photo_2_path' => $photo2Path,
            'telegram_chat_id' => $validated['telegram_chat_id'] ?? null,
            'telegram_message_id' => $validated['telegram_message_id'] ?? null,
            'raw_payload' => $this->safePayloadSnapshot($request),
        ]);

        $textFilePath = $this->writeItalianTextFile($registration, $folder, 'registrazione-ospite-'.$safeGuest.'-'.$timestamp.'.txt');
        $registration->update(['text_file_path' => $textFilePath]);

        return response()->json([
            'ok' => true,
            'message' => 'Registrazione ospite salvata.',
            'registration_id' => $registration->id,
            'booking_id' => $booking?->id,
            'room_code' => $registration->room_code,
            'arrival_date' => $registration->arrival_date?->format('Y-m-d'),
            'saved_files' => [
                'documento_foto_1' => $registration->document_photo_1_path,
                'documento_foto_2' => $registration->document_photo_2_path,
                'document_front' => $registration->document_photo_1_path,
                'document_back' => $registration->document_photo_2_path,
                'testo_italiano' => $registration->text_file_path,
            ],
        ], 201);
    }

    private function authorizeBot(Request $request): void
    {
        $expected = (string) config('services.telegram_bot.secret');
        $provided = (string) ($request->header('X-Hotel-Pineta-Bot-Secret') ?: $request->input('bot_secret'));

        abort_if($expected === '', 403, 'Telegram bot secret is not configured.');
        abort_if(! hash_equals($expected, $provided), 403, 'Wrong Telegram bot secret.');
    }

    private function mergeItalianAliases(Request $request): void
    {
        $aliases = [
            'data_arrivo' => ['arrival_date', 'giorno_arrivo'],
            'numero_camera' => ['room', 'room_code', 'camera'],
            'ospite_numero' => ['guest_index', 'guest_number', 'numero_ospite'],
            'tipo_documento' => ['document_type'],
            'cognome' => ['surname', 'last_name'],
            'nome' => ['name', 'first_name'],
            'nazionalita' => ['nationality'],
            'codice_fiscale' => ['fiscal_code', 'codice'],
            'data_nascita' => ['birth_date'],
            'luogo_nascita' => ['birth_place'],
            'sesso' => ['gender'],
            'altezza' => ['height', 'height_cm', 'altezza_cm'],
            'numero_documento' => ['document_number', 'numero', 'numero_doc'],
            'data_rilascio' => ['issue_date', 'document_issue_date', 'rilascio'],
            'data_scadenza' => ['expiry_date', 'expiration_date', 'document_expiry_date', 'scadenza'],
            'comune_emissione' => ['issuing_municipality', 'document_issuing_municipality', 'comune_di_emissione', 'luogo_rilascio', 'autorita_emissione'],
        ];

        $merged = [];
        foreach ($aliases as $target => $sources) {
            if ($request->filled($target) || $request->hasFile($target)) {
                continue;
            }

            foreach ($sources as $source) {
                if ($request->filled($source)) {
                    $merged[$target] = $request->input($source);
                    break;
                }
            }
        }

        $this->copyFirstAvailableFileAlias($request, 'documento_foto_1', ['document_front', 'document_photo_1']);
        $this->copyFirstAvailableFileAlias($request, 'documento_foto_2', ['document_back', 'document_photo_2']);

        if ($merged !== []) {
            $request->merge($merged);
        }
    }

    private function findRoomByCode(string $code): ?Room
    {
        $normalized = $this->normalizeRoomCode($code);

        return Room::query()
            ->where('is_active', true)
            ->get()
            ->first(fn (Room $room): bool => $this->normalizeRoomCode((string) $room->code) === $normalized);
    }

    private function findActiveBooking(int $roomId, Carbon $arrivalDate): ?Booking
    {
        return Booking::query()
            ->where('room_id', $roomId)
            ->whereDate('arrival_date', '<=', $arrivalDate->toDateString())
            ->whereDate('departure_date', '>', $arrivalDate->toDateString())
            ->whereIn('status', Booking::activeStatuses())
            ->latest('id')
            ->first();
    }

    private function normalizeRoomCode(string $code): string
    {
        $code = str_replace(['№', 'А', 'а'], ['', 'A', 'A'], trim($code));
        $code = preg_replace('/[^A-Za-z0-9_-]+/', '', $code) ?: 'room';

        return strtoupper($code);
    }

    private function ensureRegistrationFolder(Carbon $arrivalDate, string $roomCode): string
    {
        $folder = 'private/bookings/'.$arrivalDate->format('d.m.Y').'/'.$roomCode;
        Storage::disk('local')->makeDirectory($folder);

        return $folder;
    }

    private function copyFirstAvailableFileAlias(Request $request, string $target, array $aliases): void
    {
        if ($request->hasFile($target)) {
            return;
        }

        foreach ($aliases as $alias) {
            if ($request->hasFile($alias)) {
                $request->files->set($target, $request->file($alias));
                return;
            }
        }
    }

    private function storeIncomingFile(Request $request, array|string $fields, string $folder, string $baseFilename): ?string
    {
        $fields = (array) $fields;
        $field = collect($fields)->first(fn (string $candidate): bool => $request->hasFile($candidate));

        if (! $field) {
            return null;
        }

        $file = $request->file($field);
        if (! $file || ! $file->isValid()) {
            return null;
        }

        $extension = strtolower($file->getClientOriginalExtension() ?: $file->extension() ?: 'bin');
        $filename = $baseFilename.'.'.$extension;
        $file->storeAs($folder, $filename, 'local');

        return $folder.'/'.$filename;
    }

    private function writeItalianTextFile(PoliceGuestRegistration $registration, string $folder, string $filename): string
    {
        $path = $folder.'/'.$filename;
        Storage::disk('local')->put($path, $registration->italianTextBlock());

        return $path;
    }

    private function normalizeGender(?string $gender): ?string
    {
        $value = mb_strtolower(trim((string) $gender));
        if ($value === '') {
            return null;
        }

        return match ($value) {
            'm', 'male', 'maschio', 'м', 'муж', 'мужской' => 'M',
            'f', 'female', 'femmina', 'ж', 'жен', 'женский' => 'F',
            default => Str::upper(trim((string) $gender)),
        };
    }

    private function normalizeHeight(?string $height): ?int
    {
        $value = trim((string) $height);
        if ($value === '') {
            return null;
        }

        if (preg_match('/\d{2,3}/', $value, $matches) !== 1) {
            return null;
        }

        $heightCm = (int) $matches[0];

        return $heightCm >= 40 && $heightCm <= 260 ? $heightCm : null;
    }

    private function parseOptionalDate(?string $value): ?string
    {
        $value = trim((string) $value);
        if ($value === '') {
            return null;
        }

        $formats = ['Y-m-d', 'd.m.Y', 'd/m/Y', 'd-m-Y', 'Y/m/d'];
        foreach ($formats as $format) {
            try {
                $date = Carbon::createFromFormat($format, $value);
                if ($date !== false) {
                    return $date->toDateString();
                }
            } catch (\Throwable $e) {
                // Try the next known format before falling back to Carbon::parse.
            }
        }

        try {
            return Carbon::parse($value)->toDateString();
        } catch (\Throwable $e) {
            return null;
        }
    }

    private function safeFilePart(string $value): string
    {
        $value = Str::ascii($value);
        $value = preg_replace('/[^A-Za-z0-9_-]+/', '-', $value) ?: 'ospite';

        return trim($value, '-_') ?: 'ospite';
    }

    private function safePayloadSnapshot(Request $request): array
    {
        return collect($request->except(['bot_secret']))
            ->reject(fn ($value): bool => is_object($value))
            ->all();
    }
}
