<?php

namespace App\Services;

use App\Libs\Enum\UserTypeEnum;
use App\Models\InstantBookingRequest;
use App\Models\User;
use App\Repositories\Interfaces\BookingRepositoryInterface;
use App\Repositories\Interfaces\BookingAttachmentRepositoryInterface;
use App\Repositories\Interfaces\InstantBookingOfferRepositoryInterface;
use App\Repositories\Interfaces\InstantBookingRequestRepositoryInterface;
use App\Repositories\Interfaces\ProfessionalRepositoryInterface;
use App\Repositories\Interfaces\UserRepositoryInterface;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class InstantBookingService
{
    public function __construct(
        private readonly InstantBookingRequestRepositoryInterface $requestRepository,
        private readonly InstantBookingOfferRepositoryInterface $offerRepository,
        private readonly ProfessionalRepositoryInterface $professionalRepository,
        private readonly BookingRepositoryInterface $bookingRepository,
        private readonly BookingAttachmentRepositoryInterface $attachmentRepository,
        private readonly UserRepositoryInterface $userRepository,
        private readonly FirebaseCloudMessagingService $messagingService,
        private readonly UploadService $uploadService,
    ) {}

    public function create(array $payload, string $userUuid): array
    {
        try {
            $user = $this->user($userUuid);
            if (! $user || ! in_array($user->role_id, [UserTypeEnum::CLIENT->value, UserTypeEnum::PROFESSIONAL->value], true)) {
                return $this->error('Only client and professional accounts can create instant consultation requests.', 403);
            }
            $professionals = $this->professionalRepository->getAvailableForInstantBooking(
                (int) $payload['profession_id'],
                isset($payload['specialty_id']) ? (int) $payload['specialty_id'] : null,
                $user->id,
            );
            if ($professionals->isEmpty()) {
                return $this->error('No professionals are currently available for the selected profession.', 422);
            }
            $attachments = $payload['attachments'] ?? [];
            unset($payload['attachments']);
            $payload += ['uuid' => Str::uuid(), 'client_id' => $user->id, 'expires_at' => now()->addMinutes(10)];
            $instant = DB::transaction(function () use ($payload, $attachments) {
                $instant = $this->requestRepository->create($payload);
                foreach ($attachments as $attachment) {
                    $fileName = $this->uploadService->processAttachments($attachment, "instant-bookings/{$instant->uuid}");
                    $this->attachmentRepository->create([
                        'instant_booking_request_id' => $instant->id,
                        'file_name' => $fileName,
                        'original_name' => $attachment->getClientOriginalName(),
                        'mime_type' => $attachment->getClientMimeType() ?: $attachment->getMimeType(),
                        'file_size' => $attachment->getSize(),
                    ]);
                }
                return $instant;
            });

            $this->notifyProfessionals($instant, $professionals);
            return $this->success('Request sent to available professionals.', ['id' => $instant->uuid]);
        } catch (Exception $exception) {
            return $this->failed($exception, 'Error creating instant consultation request.');
        }
    }

    public function get(string $uuid, string $userUuid): array
    {
        try {
            $instant = $this->requestRepository->getByUuid($uuid);
            $user = $this->user($userUuid);
            if (! $instant) return $this->error('Instant consultation request not found.', 404);
            if (! $user || ($instant->client_id !== $user->id && $user->professional?->profession_id !== $instant->profession_id)) {
                return $this->error('Permission denied.', 403);
            }
            if ($instant->status === 'waiting' && $instant->expires_at->isPast()) {
                $instant = $this->requestRepository->update(['status' => 'expired'], $instant->id);
            }
            $data = $this->resource($instant);
            if ($user->professional && $instant->selected_offer_id) {
                $data['status'] = 'selected';
            }
            return $this->success('Request retrieved.', $data);
        } catch (Exception $exception) {
            return $this->failed($exception, 'Error retrieving instant consultation request.');
        }
    }

    public function pending(string $userUuid): array
    {
        try {
            $professional = $this->user($userUuid)?->professional;
            if (! $professional) return $this->error('Only professionals can view these requests.', 403);
            $requests = $this->requestRepository->getPendingForProfessional(
                $professional->profession_id, $professional->id, $professional->user_id
            )->map(fn ($instant) => $this->resource($instant));
            return $this->success('Requests retrieved.', $requests);
        } catch (Exception $exception) {
            return $this->failed($exception, 'Error retrieving instant consultation requests.');
        }
    }

    public function respond(string $uuid, array $payload, string $userUuid): array
    {
        try {
            $professional = $this->user($userUuid)?->professional;
            $instant = $this->requestRepository->getByUuid($uuid);
            if (! $professional) return $this->error('Only professionals can respond.', 403);
            if (! $instant) return $this->error('Instant consultation request not found.', 404);
            if ($instant->status !== 'waiting' || $instant->expires_at->isPast()) return $this->error('This request is no longer available.', 422);
            if ($instant->client_id === $professional->user_id) return $this->error('You cannot respond to your own instant consultation request.', 403);
            if ($professional->profession_id !== $instant->profession_id) return $this->error('Permission denied.', 403);

            $offer = $this->offerRepository->respond(
                $instant->id, $professional->id, $payload['status'], $payload['amount'] ?? null
            );
            if ($payload['status'] === 'accepted') {
                $instant->loadMissing('client');
                $this->send($instant->client, 'Professional available',
                    'A professional accepted your request. Tap to review their price.',
                    ['type' => 'instant_offer_received', 'instant_request_id' => $instant->uuid, 'recipient_role' => 'client']);
            }
            return $this->success('Response submitted.', ['offer_id' => $offer->id]);
        } catch (Exception $exception) {
            return $this->failed($exception, 'Error responding to instant consultation request.');
        }
    }

    public function select(string $uuid, int $offerId, string $userUuid): array
    {
        try {
            $user = $this->user($userUuid);
            $result = DB::transaction(function () use ($uuid, $offerId, $user) {
                $instant = $this->requestRepository->getByUuidForUpdate($uuid);
                if (! $instant) throw new \DomainException('Request not found.');
                if (! $user || $instant->client_id !== $user->id || $instant->status !== 'waiting') {
                    throw new \DomainException('This request can no longer be selected.');
                }
                $offer = $this->offerRepository->getAcceptedOffer($offerId, $instant->id);
                if (! $offer) throw new \DomainException('Professional offer not found.');
                $booking = $this->bookingRepository->create([
                    'uuid' => Str::uuid(), 'client_id' => $user->id, 'professional_id' => $offer->professional_id,
                    'profession_id' => $instant->profession_id, 'type' => 'realtime', 'date' => now()->toDateString(),
                    'time' => now()->format('H:i'), 'amount' => $offer->amount, 'status' => 'accepted',
                    'payment_status' => 'pending', 'concern' => $instant->concern,
                ]);
                // Keep the request and all accepted offers available while Maya checkout is open.
                // Payment confirmation is the point at which the selected professional is finalized.
                $this->requestRepository->update(['selected_offer_id' => $offer->id, 'booking_id' => $booking->id], $instant->id);
                $this->attachmentRepository->mapRequestAttachmentsToBooking($instant->id, $booking->id);
                return $booking;
            });
            $booking = $result;
            return $this->success('Professional selected.', ['booking_id' => $booking->uuid]);
        } catch (\DomainException $exception) {
            return $this->error($exception->getMessage(), 422);
        } catch (Exception $exception) {
            return $this->failed($exception, 'Error selecting professional.');
        }
    }

    public function cancel(string $uuid, string $userUuid): array
    {
        try {
            $instant = $this->requestRepository->getByUuid($uuid);
            $user = $this->user($userUuid);
            if (! $instant) return $this->error('Instant consultation request not found.', 404);
            if (! $user || $instant->client_id !== $user->id || ! in_array($instant->status, ['waiting', 'expired', 'selected'], true)) {
                return $this->error('Permission denied.', 403);
            }
            $this->requestRepository->update(['status' => 'cancelled'], $instant->id);
            return $this->success('Request cancelled.');
        } catch (Exception $exception) {
            return $this->failed($exception, 'Error cancelling instant consultation request.');
        }
    }

    public function retry(string $uuid, string $userUuid): array
    {
        try {
            $user = $this->user($userUuid);
            $instant = DB::transaction(function () use ($uuid, $user) {
                $instant = $this->requestRepository->getByUuidForUpdate($uuid);
                if (! $instant) throw new \DomainException('Instant consultation request not found.');
                if (! $user || $instant->client_id !== $user->id) throw new \DomainException('Permission denied.');
                if ($instant->status !== 'expired' && ! ($instant->status === 'waiting' && $instant->expires_at->isPast())) {
                    throw new \DomainException('Only an expired request can be retried.');
                }

                $this->offerRepository->deleteForRequest($instant->id);
                return $this->requestRepository->update([
                    'status' => 'waiting',
                    'selected_offer_id' => null,
                    'booking_id' => null,
                    'expires_at' => now()->addMinutes(10),
                ], $instant->id);
            });

            $this->notifyProfessionals($instant);
            return $this->success('Request sent to available professionals again.', $this->resource($instant));
        } catch (\DomainException $exception) {
            return $this->error($exception->getMessage(), 422);
        } catch (Exception $exception) {
            return $this->failed($exception, 'Error retrying instant consultation request.');
        }
    }

    private function notifyProfessionals(InstantBookingRequest $instant, $professionals = null): void
    {
        $professionals ??= $this->professionalRepository->getAvailableForInstantBooking(
            $instant->profession_id,
            $instant->specialty_id,
            $instant->client_id,
        );
        $notificationsSubmitted = 0;
        $professionalsWithToken = 0;
        foreach ($professionals as $professional) {
            if ($professional->user && ($professional->user->pushTokens()->exists() || filled($professional->user->push_token))) $professionalsWithToken++;
            if ($this->send($professional->user, 'New instant consultation',
                'A client needs a consultation now. Tap to review.',
                ['type' => 'instant_booking_request', 'instant_request_id' => $instant->uuid, 'recipient_role' => 'professional'])) {
                $notificationsSubmitted++;
            }
        }
        Log::info('Instant booking notification fan-out completed.', [
            'instant_request_id' => (string) $instant->uuid,
            'profession_id' => $instant->profession_id,
            'specialty_id' => $instant->specialty_id,
            'matched_professionals' => $professionals->count(),
            'professionals_with_token' => $professionalsWithToken,
            'notifications_submitted' => $notificationsSubmitted,
        ]);
    }

    private function user(string $uuid): ?User
    {
        $user = $this->userRepository->getByColumn('uuid', $uuid);
        $user?->loadMissing('professional');
        return $user;
    }

    private function resource(InstantBookingRequest $instant): array
    {
        $instant->loadMissing(['client.profile', 'profession', 'specialty', 'booking', 'attachments', 'offers.professional.user.profile', 'offers.professional.profession']);
        return [
            'id' => $instant->uuid, 'status' => $instant->status, 'concern' => $instant->concern,
            'profession_id' => $instant->profession_id, 'profession' => $instant->profession?->name,
            'specialty_id' => $instant->specialty_id, 'specialty' => $instant->specialty?->name,
            'attachments' => $instant->attachments->map(fn ($attachment) => $this->attachmentResource($attachment, $instant->uuid))->values(),
            'expires_at' => $instant->expires_at->toIso8601String(), 'booking_id' => $instant->booking?->uuid,
            'client_name' => $instant->client?->profile?->full_name,
            'client_image' => $this->userImage($instant->client),
            'offers' => $instant->offers->where('status', 'accepted')->map(fn ($offer) => [
                'id' => $offer->id, 'amount' => (string) $offer->amount,
                'professional_id' => $offer->professional?->id,
                'professional_uuid' => $offer->professional?->professional_uuid,
                'professional_name' => $offer->professional?->user?->profile?->full_name,
                'profession' => $offer->professional?->profession?->name,
                'salutation' => $offer->professional?->profession?->salutation,
                'gender' => $offer->professional?->user?->profile?->gender,
                'color_hex' => $offer->professional?->profession?->color_hex,
                'image' => $this->userImage($offer->professional?->user),
            ])->values(),
        ];
    }

    private function attachmentResource($attachment, string $requestUuid): array
    {
        return [
            'id' => $attachment->id,
            'name' => $attachment->original_name,
            'mime_type' => $attachment->mime_type,
            'size' => $attachment->file_size,
            'url' => $this->publicAttachmentUrl($requestUuid, $attachment->file_name),
        ];
    }

    private function publicAttachmentUrl(string $requestUuid, string $fileName): string
    {
        return asset("storage/instant-bookings/{$requestUuid}/{$fileName}");
    }

    private function userImage($user): ?string
    {
        $fileName = $user?->profile?->image;
        if (! $user || ! $fileName) return null;

        return $this->uploadService->generatePublicLink('users', [
            'id' => $user->id,
            'column' => $fileName,
        ]);
    }

    private function send(?User $user, string $title, string $body, array $data): bool
    {
        if (! $user) {
            Log::warning('Push notification skipped because the recipient has no registered token.', [
                'type' => $data['type'] ?? null,
                'instant_request_id' => $data['instant_request_id'] ?? null,
                'booking_id' => $data['booking_id'] ?? null,
            ]);
            return false;
        }

        return $this->messagingService->sendToUser($user, $title, $body, $data);
    }
    private function success(string $message, mixed $data = null): array { return ['success' => true, 'message' => $message, 'data' => $data]; }
    private function error(string $message, int $status): array { return ['success' => false, 'message' => $message, 'status' => $status]; }
    private function failed(Exception $exception, string $message): array
    {
        Log::error($message, ['errorMessage' => $exception->getMessage()]);
        return $this->error($message, 500);
    }
}
