<?php

namespace App\Services;

use App\Models\PaymentSetting;
use App\Repositories\Interfaces\BookingRepositoryInterface;
use App\Repositories\Interfaces\PaymentRepositoryInterface;
use App\Repositories\Interfaces\ProfessionalRepositoryInterface;
use App\Repositories\Interfaces\InstantBookingOfferRepositoryInterface;
use App\Repositories\Interfaces\InstantBookingRequestRepositoryInterface;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Lloricode\LaravelPaymaya\Facades\PaymayaFacade;
use Lloricode\Paymaya\DataTransferObjects\Checkout\Amount\AmountDetailDto;
use Lloricode\Paymaya\DataTransferObjects\Checkout\Amount\AmountDto;
use Lloricode\Paymaya\DataTransferObjects\Checkout\Buyer\BuyerDto;
use Lloricode\Paymaya\DataTransferObjects\Checkout\Buyer\ContactDto;
use Lloricode\Paymaya\DataTransferObjects\Checkout\CheckoutDto;
use Lloricode\Paymaya\DataTransferObjects\Checkout\ItemDto;
use Lloricode\Paymaya\DataTransferObjects\Checkout\RedirectUrlDto;
use Lloricode\Paymaya\DataTransferObjects\Checkout\TotalAmountDto;

class PaymentService
{
    /**
     * @var BookingRepositoryInterface
     */
    protected $bookingRepository;

    /**
     * @var ProfessionalRepositoryInterface
     */
    protected $professionalRepository;

    /**
     * @var PaymentRepositoryInterface
     */
    protected $paymentRepository;

    protected FirebaseCloudMessagingService $messagingService;

    protected BookingNotificationService $bookingNotificationService;

    private $response = [
        'success' => true,
        'message' => null,
    ];

    /**
     * @param  PaymentRepositoryInterface  $userRepository
     */
    public function __construct(
        BookingRepositoryInterface $bookingRepository,
        ProfessionalRepositoryInterface $professionalRepository,
        PaymentRepositoryInterface $paymentRepository,
        FirebaseCloudMessagingService $messagingService,
        BookingNotificationService $bookingNotificationService,
    ) {
        $this->bookingRepository = $bookingRepository;
        $this->professionalRepository = $professionalRepository;
        $this->paymentRepository = $paymentRepository;
        $this->messagingService = $messagingService;
        $this->bookingNotificationService = $bookingNotificationService;
    }

    /**
     * @param  mixed  $paymentDetails
     * @return [type]
     */
    public function checkout(Request $request, $tokenInfo)
    {
        try {
            $payload = $request->all();
            $booking = $this->bookingRepository->getByColumn('uuid', $payload['booking_id']);

            if (! $booking) {
                $this->response['success'] = false;
                $this->response['message'] = 'Booking not found.';

                return $this->response;
            }

            if ($booking->payment_status === 'paid') {
                $this->response['success'] = false;
                $this->response['message'] = 'Booking already paid';

                return $this->response;
            }

            if ($booking->status !== 'accepted') {
                $this->response['success'] = false;
                $this->response['message'] = 'Booking is not yet accepted';

                return $this->response;
            }

            if ($booking->unpaidDeadlinePassed()) {
                if ($this->bookingRepository->expireUnpaidBooking($booking->id)) {
                    $booking->status = 'expired';
                    $booking->loadMissing(['client.profile', 'professional.user.profile']);
                    $this->bookingNotificationService->bookingExpiredUnpaid($booking);
                }
                $this->response['success'] = false;
                $this->response['message'] = 'This booking has expired because its payment deadline or scheduled time has passed.';
                $this->response['status'] = 422;
                return $this->response;
            }

            $subtotal = (float) $booking->amount;
            $setting = Schema::hasTable('payment_settings') ? PaymentSetting::query()->first() : null;
            $convenienceFee = 0.0;
            $platformFeePercentage = 0.0;
            if ($setting?->is_active) {
                $convenienceFee = $setting->convenience_fee_type === 'fixed'
                    ? (float) $setting->convenience_fee_value
                    : $subtotal * ((float) $setting->convenience_fee_value / 100);
                $platformFeePercentage = (float) $setting->platform_fee_percentage;
            }
            $convenienceFee = round($convenienceFee, 2);
            $platformFee = round($subtotal * ($platformFeePercentage / 100), 2);
            $professionalNetAmount = round(max(0, $subtotal - $platformFee), 2);
            $amountToPay = round($subtotal + $convenienceFee, 2);

            $checkout = new CheckoutDto(
                totalAmount: new TotalAmountDto(
                    value: $amountToPay,
                    details: new AmountDetailDto(
                        serviceCharge: $convenienceFee,
                        subtotal: $subtotal
                    )
                ),
                buyer: new BuyerDto(
                    firstName: $tokenInfo['user_info']['first_name'],
                    lastName: $tokenInfo['user_info']['last_name'],
                    contact: new ContactDto(
                        phone: '',
                        email: $tokenInfo['user_info']['email']
                    )
                ),
                items: [
                    new ItemDto(
                        name: 'Booking Fee',
                        amount: new AmountDto(
                            value: $amountToPay,
                            details: new AmountDetailDto(
                                discount: 0,
                                serviceCharge: $convenienceFee,
                                shippingFee: 0,
                                tax: 0,
                                subtotal: $subtotal
                            )
                        ),
                        totalAmount: new AmountDto(
                            value: $amountToPay,
                            details: new AmountDetailDto(
                                discount: 0,
                                serviceCharge: $convenienceFee,
                                shippingFee: 0,
                                tax: 0,
                                subtotal: $subtotal
                            )
                        )
                    ),
                ],
                redirectUrl: new RedirectUrlDto(
                    success: $this->mobileRedirectUrl('success', $booking->uuid),
                    failure: $this->mobileRedirectUrl('failure', $booking->uuid),
                    cancel: $this->mobileRedirectUrl('cancel', $booking->uuid),
                ),
                requestReferenceNumber: $booking->uuid
            );

            $payload = [
                'uuid' => Str::uuid(),
                'status' => 'pending',
                'amount' => $amountToPay,
                'booking_id' => $booking->id,
            ];

            $payment = $this->paymentRepository->getBookingLatestPayment($booking->id);

            $checkoutResponse = PaymayaFacade::createCheckout($checkout);

            $this->bookingRepository->update([
                'convenience_fee_amount' => $convenienceFee,
                'platform_fee_percentage' => $platformFeePercentage,
                'platform_fee_amount' => $platformFee,
                'professional_net_amount' => $professionalNetAmount,
            ], $booking->id);

            $payload['reference_id'] = $checkoutResponse->checkoutId;

            if (! $payment || $payment->status !== 'PENDING') {
                $this->paymentRepository->create($payload);
            } else {
                $this->paymentRepository->update($payload, $payment->id);
            }

            

            $this->response['message'] = 'Payment checkout created.';
            $this->response['data'] = [
                'checkoutUrl' => $checkoutResponse,
            ];

            return $this->response;
        } catch (Exception $e) {
            Log::error('Payment Checkout = ', [$e->getMessage()]);
            $this->response['success'] = false;
            $this->response['message'] = 'Payment checkout failed.';

            return $this->response;
        }
    }

    public function processWebhook($checkoutData, $paymentStatus)
    {
        try {
            $paymentStatus = strtolower($paymentStatus);
            $bookingPaymentStatus = $paymentStatus === 'dropout' ? 'failed' : $paymentStatus;
            $payments = $this->paymentRepository->getByColumn('reference_id', $checkoutData['id']);

            if (! $payments) {
                app('log')->info('Reference Not Found', $checkoutData);

                return false;
            }

            $initialBooking = $this->bookingRepository->find($payments->booking_id);
            $latePayment = false;
            $expiredNow = false;
            $statusChanged = false;
            $booking = DB::transaction(function () use ($initialBooking, $payments, $checkoutData, $paymentStatus, $bookingPaymentStatus, &$latePayment, &$statusChanged, &$expiredNow) {
                if ($initialBooking->type === 'appointment') {
                    $this->professionalRepository->lockForBooking($initialBooking->professional_id);
                }
                $booking = $this->bookingRepository->findForUpdate($payments->booking_id);
                if (! $booking) { throw new \RuntimeException('Booking not found.'); }
                $wasPaid = $booking->payment_status === 'paid';
                // Never downgrade a settled payment when an older failure callback arrives.
                if ($wasPaid) { return $booking; }
                $statusChanged = $booking->payment_status !== $bookingPaymentStatus;
                $this->paymentRepository->update([
                    'status' => $paymentStatus,
                    'payment_response' => $checkoutData,
                ], $payments->id);
                if ($booking->type === 'appointment'
                    && ($booking->status !== 'accepted' || $booking->unpaidDeadlinePassed())) {
                    $latePayment = $bookingPaymentStatus === 'paid';
                    if (in_array($booking->status, ['pending', 'accepted'], true)) {
                        $booking->status = 'expired';
                        $expiredNow = true;
                    }
                    // Record received funds, but do not reclaim the released slot or create a call room.
                    if ($latePayment) { $booking->payment_status = 'paid'; }
                    $this->bookingRepository->update($booking->getDirty(), $booking->id);
                    return $booking;
                }
                $booking->payment_status = $bookingPaymentStatus;
                if ($bookingPaymentStatus === 'paid' && ! $booking->room_id) {
                    $booking->room_id = $this->generateUniqueRoomId();
                }
                $this->bookingRepository->update($booking->getDirty(), $booking->id);
                return $booking;
            });

            if ($expiredNow) {
                $this->bookingNotificationService->bookingExpiredUnpaid($booking);
            }
            if ($latePayment) {
                $this->bookingNotificationService->latePaymentReceived($booking);
                Log::warning('Payment received for a released appointment; refund review required', ['booking_id' => $booking->id, 'payment_id' => $payments->id]);
            } elseif ($statusChanged && $booking->status !== 'expired') {
                if ($booking->client) { $this->sendPaymentStatusNotification($booking, $bookingPaymentStatus); }
                if ($bookingPaymentStatus === 'paid') {
                    $this->finalizeInstantBooking($booking->id);
                    $this->bookingNotificationService->paymentCompleted($booking);
                }
            }

            Log::channel('daily')->error('Payment Updates', [
                'reference_id' => $checkoutData['id'] ?? 'unknown',
                'status' => $paymentStatus,
                'response' => $checkoutData,
            ]);

            return true;

        } catch (Exception $e) {
            Log::channel('daily')->error('Webhook Processing Failed - Transaction Rollback Occurred', [
                'reference_id' => $checkoutData['id'] ?? 'unknown',
                'error_message' => $e->getMessage(),
                'error_trace' => $e->getTraceAsString(),
            ]);

            return false;
        }
    }

    private function finalizeInstantBooking(int $bookingId): void
    {
        try {
            $requestRepository = app(InstantBookingRequestRepositoryInterface::class);
            $instantRequest = $requestRepository->getByBookingId($bookingId);
            if (! $instantRequest) return;

            $requestRepository->update(['status' => 'selected'], $instantRequest->id);
            if ($instantRequest->selected_offer_id) {
                app(InstantBookingOfferRepositoryInterface::class)->update(
                    ['status' => 'selected'],
                    $instantRequest->selected_offer_id,
                );
            }
        } catch (\Throwable $exception) {
            // Payment confirmation must not be rolled back by optional instant-booking finalization.
            Log::warning('Unable to finalize instant booking after payment.', [
                'booking_id' => $bookingId,
                'error' => $exception->getMessage(),
            ]);
        }
    }

    private function generateUniqueRoomId(): string
    {
        do {
            $characters = '';

            for ($index = 0; $index < 10; $index++) {
                $characters .= chr(random_int(97, 122));
            }

            $roomId = sprintf(
                '%s-%s-%s',
                substr($characters, 0, 3),
                substr($characters, 3, 4),
                substr($characters, 7, 3)
            );
        } while ($this->bookingRepository->getByColumn('room_id', $roomId));

        return $roomId;
    }

    private function sendPaymentStatusNotification($booking, string $paymentStatus): void
    {
        [$title, $body] = match ($paymentStatus) {
            'paid' => ['Payment successful', 'Your booking payment was completed successfully.'],
            'failed' => ['Payment unsuccessful', 'Your booking payment was not completed. Please try again.'],
            default => ['Payment status updated', "Your payment status is now {$paymentStatus}."],
        };

        $this->messagingService->sendToUser(
            $booking->client,
            $title,
            $body,
            [
                'type' => 'payment_status_changed',
                'booking_id' => $booking->uuid,
                'payment_status' => $paymentStatus,
            ]
        );
    }

    private function mobileRedirectUrl(string $result, string $bookingUuid): ?string
    {
        $url = config("maya-sdk.redirect_url.{$result}");

        if (! $url) {
            return null;
        }

        $separator = str_contains($url, '?') ? '&' : '?';

        return $url.$separator.http_build_query(['booking_id' => $bookingUuid]);
    }
}
