<?php

/**
 * @copyright © Melograno Ventures. All rights reserved.
 * @licence   See LICENCE.md for license details.
 */

namespace AmeliaBooking\Application\Services\Placeholder;

use AmeliaBooking\Application\Services\Coupon\AbstractCouponApplicationService;
use AmeliaBooking\Application\Services\Helper\HelperService;
use AmeliaBooking\Domain\Collection\Collection;
use AmeliaBooking\Domain\Common\Exceptions\CouponInvalidException;
use AmeliaBooking\Domain\Common\Exceptions\CouponExpiredException;
use AmeliaBooking\Domain\Common\Exceptions\CouponUnknownException;
use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException;
use AmeliaBooking\Domain\Entity\Bookable\Service\Service;
use AmeliaBooking\Domain\Entity\Booking\Event\Event;
use AmeliaBooking\Domain\Entity\Coupon\Coupon;
use AmeliaBooking\Domain\Entity\CustomField\CustomField;
use AmeliaBooking\Domain\Entity\Entities;
use AmeliaBooking\Domain\Entity\User\AbstractUser;
use AmeliaBooking\Domain\Entity\User\Customer;
use AmeliaBooking\Domain\Factory\User\UserFactory;
use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
use AmeliaBooking\Domain\Services\Settings\SettingsService;
use AmeliaBooking\Domain\ValueObjects\Number\Integer\LoginType;
use AmeliaBooking\Domain\ValueObjects\String\BookingStatus;
use AmeliaBooking\Domain\ValueObjects\String\PaymentStatus;
use AmeliaBooking\Infrastructure\Common\Container;
use AmeliaBooking\Infrastructure\Common\Exceptions\NotFoundException;
use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
use AmeliaBooking\Infrastructure\Repository\Bookable\Service\PackageCustomerRepository;
use AmeliaBooking\Infrastructure\Repository\Booking\Appointment\AppointmentRepository;
use AmeliaBooking\Infrastructure\Repository\Booking\Event\EventRepository;
use AmeliaBooking\Infrastructure\Repository\Coupon\CouponRepository;
use AmeliaBooking\Infrastructure\Repository\CustomField\CustomFieldRepository;
use AmeliaBooking\Infrastructure\Repository\User\UserRepository;
use AmeliaBooking\Infrastructure\WP\Translations\BackendStrings;
use AmeliaBooking\Infrastructure\WP\Translations\FrontendStrings;
use AmeliaBooking\Domain\ValueObjects\String\CustomFieldType;
use AmeliaBooking\Infrastructure\WP\Translations\LiteBackendStrings;
use Exception;
use Interop\Container\Exception\ContainerException;
use DateTime;
use Slim\Exception\ContainerValueNotFoundException;

/**
 * Class PlaceholderService
 *
 * @package AmeliaBooking\Application\Services\Placeholder
 */
abstract class PlaceholderService implements PlaceholderServiceInterface
{
    /** @var Container */
    protected $container;

    /**
     * ProviderApplicationService constructor.
     *
     * @param Container $container
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    /**
     * @param string $text
     * @param array  $data
     *
     * @return mixed
     */
    public function applyPlaceholders($text, $data)
    {
        unset($data['icsFiles']);

        unset($data['providersAppointments']);

        unset($data['invoice_items_booking']);
        unset($data['invoice_items_extras']);
        unset($data['invoice_items_event']);
        unset($data['invoice_dates']);
        unset($data['invoice_dates_xml']);
        unset($data['items']);
        unset($data['qr_code_tickets']);

        $data = array_filter($data, function ($key) {
            return strpos($key, 'invoice_custom_field') !== 0;
        }, ARRAY_FILTER_USE_KEY);

        $placeholders = array_map(
            function ($placeholder) {
                return "%{$placeholder}%";
            },
            array_keys($data)
        );

        if ($text && strpos($text, '%amelia_dynamic_placeholder_') !== false) {
            $lastPos = 0;

            $dynamicPlaceholderStart = '%amelia_dynamic_placeholder_';

            while (($lastPos = strpos($text, $dynamicPlaceholderStart, $lastPos)) !== false) {
                $subText = substr($text, $lastPos + 1);

                $dynamicPlaceholder = substr($subText, 0, strpos($subText, '%'));

                $placeholders[] = '%' . $dynamicPlaceholder . '%';

                $data[$dynamicPlaceholder] =  apply_filters(
                    $dynamicPlaceholder,
                    $data
                );

                $lastPos = $lastPos + strlen($dynamicPlaceholderStart);
            }
        }

        return str_replace($placeholders, array_values($data), $text);
    }

    /**
     * @return array
     *
     * @throws ContainerException
     */
    public function getPlaceholdersDummyData($type)
    {
        /** @var SettingsService $settingsService */
        $settingsService = $this->container->get('domain.settings.service');

        /** @var string $paragraphStart */
        $paragraphStart = $type === 'email' ? '<p>' : '';

        /** @var string $paragraphEnd */
        $paragraphEnd = $type === 'email' ? '</p>' : ($type === 'whatsapp' ? '; ' : PHP_EOL);

        $companySettings = $settingsService->getCategorySettings('company');

        $timezone = get_option('timezone_string');

        return array_merge(
            [
            'booked_customer'     =>
                $paragraphStart .
                BackendStrings::get('ph_customer_full_name') .
                ': John Micheal Doe ' .
                $paragraphEnd .
                $paragraphStart .
                BackendStrings::get('ph_customer_phone') .
                ': 193-951-2600 ' .
                $paragraphEnd .
                $paragraphStart .
                BackendStrings::get('ph_customer_email') .
                ': customer@domain.com ' .
                $paragraphEnd,
            'company_address'     => $companySettings['address'],
            'company_country'     => $companySettings['countryCode'],
            'company_name'        => $companySettings['name'],
            'company_phone'       => $companySettings['phone'],
            'company_website'     => $companySettings['website'],
            'company_vat_number'  => $companySettings['vat'],
            'company_email'       => !empty($companySettings['email']) ? $companySettings['email'] : '',
            'customer_email'      => 'customer@domain.com',
            'customer_first_name' => 'John',
            'customer_last_name'  => 'Doe',
            'customer_full_name'  => 'John Doe',
            'customer_phone'      => '193-951-2600',
            'customer_note'       => 'Customer Note',
            'customer_panel_url'  => $this->container->get('domain.settings.service')->getSetting('roles', 'customerCabinet')['pageUrl'],
            'coupon_used'         => 'code123',
            'number_of_persons'   => 2,
            'time_zone'           => $timezone,
            'employee_email'      => 'employee@domain.com',
            'employee_first_name' => 'Richard',
            'employee_last_name'  => 'Roe',
            'employee_full_name'  => 'Richard Roe',
            'employee_phone'      => '150-698-1858',
            'employee_note'       => 'Employee Note',
            'employee_description' => 'Employee Description',
            'employee_panel_url'  => 'https://your_site.com/employee-panel',
            'location_address'        => $companySettings['address'] ? $companySettings['address'] : 'Address 123',
            'location_phone'          => $companySettings['phone'],
            'location_name'           => 'Location Name',
            'location_latitude'       => '40.748441',
            'location_longitude'      => '-73.987853',
            'location_description'    => 'Location Description',
            ],
            $this->getEntityPlaceholdersDummyData($type)
        );
    }

    /**
     * @param string|null $locale
     *
     * @return array
     */
    public function getCompanyData($locale = null)
    {
        /** @var SettingsService $settingsService */
        $settingsService = $this->container->get('domain.settings.service');

        /** @var HelperService $helperService */
        $helperService = $this->container->get('application.helper.service');

        $companySettings = $settingsService->getCategorySettings('company');

        $companyName = $helperService->getBookingTranslation(
            $locale,
            json_encode($companySettings['translations']),
            'name'
        ) ?: $companySettings['name'];

        return [
            'company_address' => $companySettings['address'],
            'company_country' => $companySettings['countryCode'],
            'company_name'    => $companyName,
            'company_phone'   => $companySettings['phone'],
            'company_website' => $companySettings['website'],
            'company_vat_number' => $companySettings['vat'],
            'company_email'   => !empty($companySettings['email']) ? $companySettings['email'] : null,
            'company_logo'    => $companySettings['pictureThumbPath']
        ];
    }

    /**
     * @param array  $appointment
     * @param string $type
     * @param null   $bookingKey
     * @param null   $token
     *
     * @return array
     *
     * @throws ContainerException
     */
    protected function getBookingData($appointment, $type, $bookingKey = null, $token = null, $depositEnabled = null, $isGroup = null, $invoice = false)
    {
        /** @var HelperService $helperService */
        $helperService = $this->container->get('application.helper.service');

        /** @var string $break */
        $break = $type === 'email' ? '<p><br></p>' : ($type === 'whatsapp' ? '; ' : PHP_EOL);

        $couponsUsed = [];

        $payment = null;

        $invoiceItem = [];

        $paymentLinks = [
            'payment_link_woocommerce' => '',
            'payment_link_stripe' => '',
            'payment_link_paypal' => '',
            'payment_link_razorpay' => '',
            'payment_link_mollie' => '',
            'payment_link_square' => '',
            'payment_link_barion' => ''
        ];

        $couponDiscount = 0;

        $amountData = [
            'price'     => 0,
            'discount'  => 0,
            'deduction' => 0,
        ];

        // If notification is for provider: Appointment price will be sum of all bookings prices
        // If notification is for customer: Appointment price will be price of his booking
        if ($bookingKey === null) {
            $numberOfPersonsData = [
                AbstractUser::USER_ROLE_PROVIDER => [
                    BookingStatus::APPROVED => 0,
                    BookingStatus::PENDING  => 0,
                    BookingStatus::CANCELED => 0,
                    BookingStatus::REJECTED => 0,
                    BookingStatus::NO_SHOW  => 0,
                    BookingStatus::WAITING  => 0,
                ]
            ];

            foreach ((array)$appointment['bookings'] as $customerBooking) {
                $amountData = $this->getAmountData($customerBooking, $appointment);

                $expirationDate = null;

                if (!empty($customerBooking['coupon']['expirationDate'])) {
                    $expirationDate = $customerBooking['coupon']['expirationDate'];
                }

                $startDate = null;

                if (!empty($customerBooking['coupon']['startDate'])) {
                    $startDate = $customerBooking['coupon']['startDate'];
                }

                if (($amountData['discount'] || $amountData['deduction']) && !empty($customerBooking['info'])) {
                    $customerData = json_decode($customerBooking['info'], true);

                    if (!$customerData) {
                        $customerData = [
                            'firstName' => $customerBooking['customer']['firstName'],
                            'lastName'  => $customerBooking['customer']['lastName'],
                        ];
                    }

                    $couponsUsed[] =
                        BackendStrings::get('customer') . ': ' .
                        $customerData['firstName'] . ' ' . $customerData['lastName'] . ' ' . $break .
                        BackendStrings::get('code') . ': ' .
                        $customerBooking['coupon']['code'] . ' ' . $break .
                        ($amountData['discount'] ? BackendStrings::get('discount_amount') . ': ' .
                            $helperService->getFormattedPrice($amountData['discount']) . ' ' . $break : '') .
                        ($amountData['deduction'] ? BackendStrings::get('deduction') . ': ' .
                            $helperService->getFormattedPrice($amountData['deduction']) . ' ' . $break : '') .
                        ($startDate ? BackendStrings::get('start_date') . ': ' .
                            $startDate . ' ' . $break : '') .
                        ($expirationDate ? BackendStrings::get('expiration_date') . ': ' .
                            $expirationDate : '');
                }

                $numberOfPersonsData[AbstractUser::USER_ROLE_PROVIDER][$customerBooking['status']] +=
                    empty($customerBooking['ticketsData']) ? $customerBooking['persons'] : array_sum(array_column($customerBooking['ticketsData'], 'persons'));

                $payment = !empty($customerBooking['payments'][0]) ? $customerBooking['payments'][0] : null;
            }

            $numberOfPersons = [];

            foreach ($numberOfPersonsData[AbstractUser::USER_ROLE_PROVIDER] as $key => $value) {
                if ($value) {
                    $numberOfPersons[] = BackendStrings::get($key) . ': ' . $value;
                }
            }

            $numberOfPersons = implode($break, $numberOfPersons);

            $icsFiles = !empty($appointment['bookings'][0]['icsFiles']) ? $appointment['bookings'][0]['icsFiles'] : [];
        } else {
            $amountData = $this->getAmountData($appointment['bookings'][$bookingKey], $appointment, $invoice);

            $couponDiscount = $amountData['discount'] + $amountData['deduction'];

            $expirationDate = null;

            if (!empty($appointment['bookings'][$bookingKey]['coupon']['expirationDate'])) {
                $expirationDate = $appointment['bookings'][$bookingKey]['coupon']['expirationDate'];
            }

            $startDate = null;

            if (!empty($appointment['bookings'][$bookingKey]['coupon']['startDate'])) {
                $startDate = $appointment['bookings'][$bookingKey]['coupon']['startDate'];
            }

            if (!empty($appointment['bookings'][$bookingKey]['coupon']['code'])) {
                $couponsUsed[] =
                    $appointment['bookings'][$bookingKey]['coupon']['code'] . ' ' . $break .
                    ($amountData['discount'] ? BackendStrings::get('discount_amount') . ': ' .
                        $helperService->getFormattedPrice($amountData['discount']) . ' ' . $break : '') .
                    ($amountData['deduction'] ? BackendStrings::get('deduction') . ': ' .
                        $helperService->getFormattedPrice($amountData['deduction']) . ' ' . $break : '') .
                    ($startDate ? BackendStrings::get('start_date') . ': ' .
                        $startDate . ' ' . $break : '') .
                    ($expirationDate ? BackendStrings::get('expiration_date') . ': ' .
                        $expirationDate : '');
            }

            $numberOfPersons =
                empty($appointment['bookings'][$bookingKey]['ticketsData']) ?
                $appointment['bookings'][$bookingKey]['persons'] :
                    array_sum(array_column($appointment['bookings'][$bookingKey]['ticketsData'], 'persons'));

            $invoiceItem['invoice_qty']          = $amountData['qty'];
            $invoiceItem['invoice_unit_price']   = $amountData['unit_price'];
            $invoiceItem['invoice_subtotal']     = $amountData['subtotal'];
            $invoiceItem['invoice_tax']          = $amountData['tax'];
            $invoiceItem['invoice_tax_rate']     = $amountData['tax_rate'];
            $invoiceItem['invoice_tax_excluded'] = $amountData['tax_excluded'];
            $invoiceItem['invoice_tax_type']     = $amountData['tax_type'];
            $invoiceItem['total_tax']            = $amountData['total_tax'];
            $invoiceItem['invoice_extras_items']   = !empty($amountData['extras_items']) ? $amountData['extras_items'] : null;
            $invoiceItem['invoice_tickets_tax']  = !empty($amountData['tickets_tax']) ? $amountData['tickets_tax'] : null;
            $invoiceItem['service_discount']  = !empty($amountData['service_discount']) ? $amountData['service_discount'] : null;

            $icsFiles = !empty($appointment['bookings'][$bookingKey]['icsFiles']) ? $appointment['bookings'][$bookingKey]['icsFiles'] : [];

            $payment = !empty($appointment['bookings'][$bookingKey]['payments'][0]) ? $appointment['bookings'][$bookingKey]['payments'][0] : null;

            $invoiceItem['invoice_paid_amount'] = 0;
            $invoiceItem['invoice_method']      = '';
            foreach (!empty($appointment['bookings'][$bookingKey]['payments']) ? $appointment['bookings'][$bookingKey]['payments'] : [] as $p) {
                if ($p['status'] === PaymentStatus::PARTIALLY_PAID || $p['status'] === PaymentStatus::PAID) {
                    $invoiceItem['invoice_paid_amount'] += $p['amount'];
                    $invoiceItem['invoice_method']       = $p['gateway'];
                }
            }

            $invoiceItem['invoice_discount'] = !empty($amountData['full_discount']) && $amountData['full_discount'] > 0 ? $amountData['full_discount'] : 0;


            if (!empty($payment['paymentLinks'])) {
                foreach ($payment['paymentLinks'] as $paymentType => $paymentLink) {
                    $paymentLinks[$paymentType] = $type === 'email' ? '<a href="' . $paymentLink . '">' . $paymentLink . '</a>' : $paymentLink;
                }
            }
        }

        $depositAmount = null;
        if (!empty($appointment['deposit']) || $depositEnabled) {
            $depositAmount = $payment ? $payment['amount'] : 0;
        }
        $paymentType = '';
        if ($payment) {
            switch ($payment['gateway']) {
                case 'onSite':
                    $paymentType = BackendStrings::get('on_site');
                    break;
                case 'wc':
                    $paymentType = BackendStrings::get('wc_name');
                    break;
                case 'square':
                    $paymentType = BackendStrings::get('square');
                    break;
                default:
                    $paymentType = BackendStrings::get($payment['gateway']);
                    break;
            }
        }

        $appointmentPrice = $helperService->getFormattedPrice($amountData['price'] >= 0 ? $amountData['price'] : 0);

        $paymentDueAmount = $payment ?
            $helperService->getFormattedPrice(
                ($amountData['price'] >= 0 ? $amountData['price'] : 0) -
                ($payment['amount'] - (!empty($payment['wcItemTaxValue']) ? $payment['wcItemTaxValue'] : 0))
            ) : '';

        $bookingKeyForEmployee = null;

        if ($bookingKey === null || $isGroup) {
            $bookingKeyForEmployee = $isGroup ?
                $appointment['bookings'][$bookingKey]['id'] : $this->getBookingKeyForEmployee($appointment);
        }

        /** @var SettingsService $settingsService */
        $settingsService = $this->container->get('domain.settings.service');

        $dateFormat = $settingsService->getSetting('wordpress', 'dateFormat');

        $customerWaiting = $bookingKey !== null && $appointment['bookings'][$bookingKey]['status'] === BookingStatus::WAITING;

        return array_merge(
            $paymentLinks,
            [
                "appointment_price" => $appointmentPrice,
                "booking_price"     => $appointmentPrice,
                "{$appointment['type']}_cancel_url" =>
                    $bookingKey !== null && isset($appointment['bookings'][$bookingKey]['id']) ?
                        AMELIA_ACTION_URL . '/bookings/cancel/' . $appointment['bookings'][$bookingKey]['id'] .
                        ($token ? '&token=' . $token : '') . "&type={$appointment['type']}" : '',
                'appointment_approve_url' =>
                    ($bookingKeyForEmployee !== null || $customerWaiting) ? (AMELIA_ACTION_URL . '/bookings/success/' .
                        ($customerWaiting ? $appointment['bookings'][$bookingKey]['id'] : $bookingKeyForEmployee) .
                        '&token=' . $token) : '',
                'appointment_reject_url' =>
                    $bookingKeyForEmployee !== null ? (AMELIA_ACTION_URL . '/bookings/reject/' . $bookingKeyForEmployee .
                        '&token=' . $token) : '',
                "{$appointment['type']}_deposit_payment"    => $depositAmount !== null ? $helperService->getFormattedPrice($depositAmount) : '',
                'payment_type'                      => $paymentType,
                'payment_status'                    => $payment ? $payment['status'] : '',
                'payment_gateway'                   => $payment ? $payment['gateway'] : '',
                'payment_created'                   => $payment && !empty($payment['created'])
                    ? date_i18n($dateFormat, strtotime($payment['created']))
                    : '',
                'payment_created_xml'               => $payment && !empty($payment['created'])
                   ? date_i18n('Y-m-d', strtotime($payment['created']))
                   : '',
                'payment_invoice_number'            => $payment ? $payment['invoiceNumber'] : '',
                'payment_gateway_title'             => $payment ? $payment['gatewayTitle'] : '',
                "payment_due_amount"                => $paymentDueAmount,
                'number_of_persons'                 => $numberOfPersons,
                'coupon_used'                       => $couponsUsed ? implode($break, $couponsUsed) : '',
                'icsFiles'                          => $icsFiles,
                'invoice_items_booking'             => [$invoiceItem]
            ]
        );
    }

    /** @noinspection MoreThanThreeArgumentsInspection */
    /**
     * @param array $appointment
     * @param string $type
     * @param null $bookingKey
     * @param Customer $customerEntity
     *
     * @return array
     *
     * @throws \Slim\Exception\ContainerException
     * @throws \InvalidArgumentException
     * @throws \Slim\Exception\ContainerValueNotFoundException
     * @throws NotFoundException
     * @throws QueryExecutionException
     * @throws ContainerException
     * @throws \Exception
     */
    public function getCustomersData($appointment, $type, $bookingKey = null, $customerEntity = null)
    {
        /** @var UserRepository $userRepository */
        $userRepository = $this->container->get('domain.users.repository');

        /** @var string $paragraphStart */
        $paragraphStart = $type === 'email' ? '<p>' : '';

        /** @var string $paragraphEnd */
        $paragraphEnd = $type === 'email' ? '</p>' : ($type === 'whatsapp' ? '; ' : PHP_EOL);

        // If the data is for employee
        if ($bookingKey === null) {
            $customers = [];
            $customerInformationData = [];

            $hasApprovedOrPendingStatus = in_array(
                BookingStatus::APPROVED,
                array_column($appointment['bookings'], 'status'),
                true
            ) ||
                in_array(
                    BookingStatus::PENDING,
                    array_column($appointment['bookings'], 'status'),
                    true
                );

            $bookedCustomerFullName = '';
            $bookedCustomerEmail    = '';
            $bookedCustomerPhone    = '';

            foreach ((array)$appointment['bookings'] as $customerBooking) {
                /** @var AbstractUser $customer */
                $customer = $userRepository->getById($customerBooking['customerId']);

                if (
                    (!$hasApprovedOrPendingStatus && $customerBooking['isChangedStatus']) ||
                    ($customerBooking['status'] !== BookingStatus::CANCELED && $customerBooking['status'] !== BookingStatus::REJECTED)
                ) {
                    if ($customerBooking['info']) {
                        $customerInformationData[] = json_decode($customerBooking['info'], true);
                    } else {
                        $customerInformationData[] = [
                            'firstName' => $customer->getFirstName()->getValue(),
                            'lastName'  => $customer->getLastName()->getValue(),
                            'phone'     => $customer->getPhone() ? $customer->getPhone()->getValue() : '',
                        ];
                    }

                    $customers[] = $customer;
                }

                if ($customerBooking['isChangedStatus']) {
                    $bookedCustomerFullName = $customer->getFullName();
                    $bookedCustomerEmail    = $customer->getEmail() ? $customer->getEmail()->getValue() : '';
                    $bookedCustomerPhone    = $customer->getPhone() ? $customer->getPhone()->getValue() : '';
                }
            }

            $phones = '';
            foreach ($customerInformationData as $key => $info) {
                if ($info['phone']) {
                    $phones .= $info['phone'] . ', ';
                } else {
                    $phones .= $customers[$key]->getPhone() ? $customers[$key]->getPhone()->getValue() . ', ' : '';
                }
            }

            $bookedCustomer =
                $paragraphStart . BackendStrings::get('ph_customer_full_name') . ': ' . $bookedCustomerFullName . $paragraphEnd;

            $bookedCustomer .=
                $bookedCustomerPhone ?
                    $paragraphStart . BackendStrings::get('ph_customer_phone') . ': ' . $bookedCustomerPhone . $paragraphEnd :
                    '';
            $bookedCustomer .=
                $bookedCustomerEmail ?
                    $paragraphStart . BackendStrings::get('ph_customer_email') . ': ' . $bookedCustomerEmail . $paragraphEnd :
                    '';

            return [
                'booked_customer'     => $paragraphStart ?
                    substr($bookedCustomer, 3, strlen($bookedCustomer) - 7) : $bookedCustomer,
                'customer_email'      => implode(
                    ', ',
                    array_map(
                        function ($customer) {
                            /** @var Customer $customer */
                            return $customer->getEmail()->getValue();
                        },
                        $customers
                    )
                ),
                'customer_first_name' => implode(
                    ', ',
                    array_map(
                        function ($info) {
                            return $info['firstName'];
                        },
                        $customerInformationData
                    )
                ),
                'customer_last_name'  => implode(
                    ', ',
                    array_map(
                        function ($info) {
                            return $info['lastName'];
                        },
                        $customerInformationData
                    )
                ),
                'customer_full_name'  => implode(
                    ', ',
                    array_map(
                        function ($info) {
                            return $info['firstName'] . ' ' . $info['lastName'];
                        },
                        $customerInformationData
                    )
                ),
                'customer_phone'      => substr($phones, 0, -2),
                'customer_phone_local' =>  str_replace('+', '', substr($phones, 0, -2)),
                'customer_note'       => implode(
                    ', ',
                    array_map(
                        function ($customer) {
                            /** @var Customer $customer */
                            return $customer->getNote() ? $customer->getNote()->getValue() : '';
                        },
                        $customers
                    )
                )
            ];
        }

        // If data is for customer
        /** @var Customer $customer */
        $customer = $customerEntity ?: (
            !empty($appointment['bookings'][$bookingKey]['customer'])
                ? UserFactory::create($appointment['bookings'][$bookingKey]['customer'])
                : $userRepository->getById($appointment['bookings'][$bookingKey]['customerId'])
        );

        $info = !empty($appointment['bookings'][$bookingKey]['info']) ?
            json_decode($appointment['bookings'][$bookingKey]['info']) : null;

        if ($info && $info->phone) {
            $phone = $info->phone;
        } else {
            $phone = $customer->getPhone() ? $customer->getPhone()->getValue() : '';
        }

        /** @var HelperService $helperService */
        $helperService = $this->container->get('application.helper.service');

        return [
            'customer_email'      => $customer->getEmail() ? $customer->getEmail()->getValue() : '',
            'customer_first_name' => $info ? $info->firstName : $customer->getFirstName()->getValue(),
            'customer_last_name'  => $info ? $info->lastName : $customer->getLastName()->getValue(),
            'customer_full_name'  => $info ? $info->firstName . ' ' . $info->lastName : $customer->getFullName(),
            'customer_phone'      => $phone,
            'customer_phone_country' => $customer->getCountryPhoneIso() ? $customer->getCountryPhoneIso()->getValue() : null,
            'customer_phone_local' => !empty($phone) ? str_replace('+', '', $phone) : '',
            'customer_note'       => $customer->getNote() ? $customer->getNote()->getValue() : '',
            'customer_panel_url'  => $helperService->getCustomerCabinetUrl(
                $customer->getEmail()->getValue(),
                $type,
                !empty($appointment['bookingStart']) ? explode(' ', $appointment['bookingStart'])[0] : null,
                !empty($appointment['bookingEnd']) ? explode(' ', $appointment['bookingEnd'])[0] : null,
                $info && property_exists($info, 'locale') ? $info->locale : ''
            )
        ];
    }

    /**
     * @param array $appointment
     * @param string $type
     * @param null $bookingKey
     *
     * @return array
     * @throws \Slim\Exception\ContainerValueNotFoundException
     * @throws QueryExecutionException
     * @throws \Exception
     */
    public function getCustomFieldsData($appointment, $type, $bookingKey = null)
    {
        /** @var SettingsService $settingsService */
        $settingsService = $this->container->get('domain.settings.service');

        $dateFormat = $settingsService->getSetting('wordpress', 'dateFormat');

        $customFieldsData = [];

        $bookingCustomFieldsKeys = [];

        if ($bookingKey === null) {
            $sendAllCustomFields =
                $settingsService->getSetting('notifications', 'sendAllCF') ||
                (array_key_exists('sendCF', $appointment) && $appointment['sendCF']) ||
                (array_key_exists('sendForAllBookings', $appointment) && $appointment['sendForAllBookings']);
            foreach ($appointment['bookings'] as $booking) {
                if (
                    (!$booking['isChangedStatus'] || (array_key_exists('isLastBooking', $booking) && !$booking['isLastBooking']))
                    && !(isset($appointment['isRescheduled']) ? $appointment['isRescheduled'] : false) && !$sendAllCustomFields
                ) {
                    continue;
                }

                if (
                    sizeof($appointment['bookings']) > 1 &&
                    ($booking['status'] === BookingStatus::CANCELED || $booking['status'] === BookingStatus::REJECTED)
                ) {
                    continue;
                }

                $bookingCustomFields = !empty($booking['customFields']) ? json_decode($booking['customFields'], true) : null;

                if ($booking['customerId'] && (!isset($booking['customer']) || !isset($booking['customer']['customFields']))) {
                    /** @var UserRepository $userRepository */
                    $userRepository = $this->container->get('domain.users.repository');

                    $booking['customer'] = $userRepository->getById($booking['customerId'])->toArray();
                }

                $customerCustomFields = !empty($booking['customer']['customFields']) ? json_decode($booking['customer']['customFields'], true) : null;

                if ($customerCustomFields) {
                    $bookingCustomFields =  $bookingCustomFields ? ($bookingCustomFields + $customerCustomFields) : $customerCustomFields;
                }


                if ($bookingCustomFields) {
                    foreach ($bookingCustomFields as $bookingCustomFieldKey => $bookingCustomField) {
                        if (!empty($bookingCustomField['value']) && !empty($bookingCustomField['type'])) {
                            if ($bookingCustomField['type'] === 'datepicker') {
                                $bookingCustomField['value'] = $this->formatDatepickerValue($bookingCustomField['value'], $dateFormat);
                            }

                            if (
                                $bookingCustomField['type'] === 'file' &&
                                (!empty($appointment['provider']) || !empty($appointment['providers']))
                            ) {
                                /** @var HelperService $helperService */
                                $helperService = $this->container->get('application.helper.service');

                                /** @var array $jwtSettings */
                                $jwtSettings = $settingsService->getSetting('roles', 'urlAttachment');

                                $provider_email = !empty($appointment['provider']) ?
                                    $appointment['provider']['email'] : $appointment['providers'][0]['email'];

                                $token = $helperService->getGeneratedJWT(
                                    $provider_email,
                                    $jwtSettings['headerJwtSecret'],
                                    DateTimeService::getNowDateTimeObject()->getTimestamp() + $jwtSettings['tokenValidTime'],
                                    LoginType::AMELIA_URL_TOKEN
                                );

                                $files = '';

                                if ($bookingCustomField['value']) {
                                    $entityId = $booking['id'];

                                    if ($customerCustomFields && array_key_exists($bookingCustomFieldKey, $customerCustomFields)) {
                                        $entityId = $booking['customerId'];
                                    }

                                    foreach ($bookingCustomField['value'] as $index => $file) {
                                        $files .= '<a href="'
                                            . AMELIA_ACTION_URL . '/fields/' . $bookingCustomFieldKey . '/' . $entityId . '/' . $index . '&token=' . $token
                                            . '">' . $file['name'] . '</a>';
                                    }

                                    $bookingCustomField['value'] = $files;
                                }
                            }

                            if (
                                $bookingCustomField['type'] === 'file' &&
                                (empty($appointment['provider']) && empty($appointment['providers']))
                            ) {
                                continue;
                            }

                            if (array_key_exists('custom_field_' . $bookingCustomFieldKey, $customFieldsData)) {
                                $value = $bookingCustomField['type'] === CustomFieldType::ADDRESS ? (
                                    $type === 'email' ?
                                        '<a href="https://maps.google.com/?q=' .
                                        $bookingCustomField['value'] . '" target="_blank">' .  $bookingCustomField['value'] .
                                        '</a>' :
                                        'https://maps.google.com/?q=' . str_replace(' ', '+', $bookingCustomField['value'])
                                ) : $bookingCustomField['value'];
                                $customFieldsData['custom_field_' . $bookingCustomFieldKey]
                                    .= is_array($value)
                                    ? '; ' . implode('; ', $value) :
                                    '; ' . $value;
                            } else {
                                $value = $bookingCustomField['type'] === CustomFieldType::ADDRESS ? (
                                $type === 'email' ?
                                    '<a href="https://maps.google.com/?q=' .
                                    $bookingCustomField['value'] . '" target="_blank">' .  $bookingCustomField['value'] .
                                    '</a>' :
                                    'https://maps.google.com/?q=' . str_replace(' ', '+', $bookingCustomField['value'])
                                ) : $bookingCustomField['value'];
                                $customFieldsData['custom_field_' . $bookingCustomFieldKey] =
                                    is_array($value)
                                        ? implode('; ', $value) : $value;
                            }

                            $bookingCustomFieldsKeys[(int)$bookingCustomFieldKey] = true;
                        }
                    }
                }
            }
        } else {
            if (!empty($appointment['bookings'][$bookingKey]['customFields'])) {
                $bookingCustomFields = !is_array($appointment['bookings'][$bookingKey]['customFields']) ?
                    json_decode($appointment['bookings'][$bookingKey]['customFields'], true) :
                    $appointment['bookings'][$bookingKey]['customFields'];
            } else {
                $bookingCustomFields = [];
            }

            if (
                !empty($appointment['bookings'][$bookingKey]['customerId']) &&
                (!isset($appointment['bookings'][$bookingKey]['customer']) || !isset($appointment['bookings'][$bookingKey]['customer']['customFields']))
            ) {
                /** @var UserRepository $userRepository */
                $userRepository = $this->container->get('domain.users.repository');

                $appointment['bookings'][$bookingKey]['customer'] = $userRepository->getById($appointment['bookings'][$bookingKey]['customerId'])->toArray();
            }

            if (!empty($appointment['bookings'][$bookingKey]['customer']['customFields'])) {
                $customerCustomFields = !is_array($appointment['bookings'][$bookingKey]['customer']['customFields']) ?
                    json_decode($appointment['bookings'][$bookingKey]['customer']['customFields'], true) :
                    $appointment['bookings'][$bookingKey]['customer']['customFields'];

                $bookingCustomFields += $customerCustomFields ?? [];
            }

            if ($bookingCustomFields) {
                foreach ((array)$bookingCustomFields as $bookingCustomFieldKey => $bookingCustomField) {
                    $bookingCustomFieldsKeys[(int)$bookingCustomFieldKey] = true;

                    if (
                        is_array($bookingCustomField) &&
                        array_key_exists('type', $bookingCustomField) &&
                        $bookingCustomField['type'] === 'file'
                    ) {
                        continue;
                    }

                    if (
                        is_array($bookingCustomField) &&
                        array_key_exists('type', $bookingCustomField) &&
                        $bookingCustomField['type'] === 'datepicker' &&
                        !empty($bookingCustomField['value'])
                    ) {
                        $bookingCustomField['value'] = $this->formatDatepickerValue($bookingCustomField['value'], $dateFormat);
                    }

                    $rawValue = '';
                    if (isset($bookingCustomField['value'])) {
                        $rawValue = is_array($bookingCustomField['value'])
                            ? implode('; ', $bookingCustomField['value']) : $bookingCustomField['value'];
                        $value = $bookingCustomField['type'] === CustomFieldType::ADDRESS ? (
                            $type === 'email' ?
                                '<a href="https://maps.google.com/?q=' .
                                $rawValue . '" target="_blank">' . $rawValue .
                                '</a>' :
                                'https://maps.google.com/?q=' . str_replace(' ', '+', $rawValue)
                        ) : $rawValue;
                        $customFieldsData['custom_field_' . $bookingCustomFieldKey] = $value;
                    } else {
                        $customFieldsData['custom_field_' . $bookingCustomFieldKey] = '';
                    }

                    $customFieldsData['invoice_custom_field_' . $bookingCustomFieldKey] = [
                        'label' => $bookingCustomField['label'],
                        'type'  => $bookingCustomField['type'],
                        'value' => $rawValue ?: '/',
                        'components' => $bookingCustomField['components'] ?? null
                    ];
                }
            }
        }

        /** @var CustomFieldRepository $customFieldRepository */
        $customFieldRepository = $this->container->get('domain.customField.repository');

        /** @var Collection $customFields */
        $customFields = $customFieldRepository->getAll();

        /** @var CustomField $customField */
        foreach ($customFields->getItems() as $customField) {
            if (!array_key_exists($customField->getId()->getValue(), $bookingCustomFieldsKeys)) {
                $customFieldsData['custom_field_' . $customField->getId()->getValue()] = '';
            }

            if (array_key_exists('invoice_custom_field_' . $customField->getId()->getValue(), $customFieldsData)) {
                if (!$customField->getIncludeInInvoice() || !$customField->getIncludeInInvoice()->getValue()) {
                    unset($customFieldsData['invoice_custom_field_' . $customField->getId()->getValue()]);
                } else {
                    $customFieldsData['invoice_custom_field_' . $customField->getId()->getValue()]['label'] =
                        $customField->getLabel()->getValue();
                }
            } elseif ($customField->getIncludeInInvoice() && $customField->getIncludeInInvoice()->getValue()) {
                $customFieldsData['invoice_custom_field_' . $customField->getId()->getValue()] = [
                    'label' => $customField->getLabel()->getValue(),
                    'type'  => $customField->getType()->getValue(),
                    'value' => '/',
                    'components' => null
                ];
            }

            if ($customField->getType()->getValue() === 'content') {
                switch ($appointment['type']) {
                    case (Entities::APPOINTMENT):
                        /** @var Service $service */
                        foreach ($customField->getServices()->getItems() as $service) {
                            if ($service->getId()->getValue() === $appointment['serviceId']) {
                                $customFieldsData['custom_field_' . $customField->getId()->getValue()] =
                                    $customField->getLabel()->getValue();
                                break;
                            }
                        }

                        break;

                    case (Entities::EVENT):
                        /** @var Event $event */
                        foreach ($customField->getEvents()->getItems() as $event) {
                            if ($event->getId()->getValue() === $appointment['id']) {
                                $customFieldsData['custom_field_' . $customField->getId()->getValue()] =
                                    $customField->getLabel()->getValue();
                                break;
                            }
                        }

                        break;
                }
            }
        }

        return $customFieldsData;
    }

    /**
     * @param array  $appointment
     * @param string $type
     * @param null   $bookingKey
     *
     * @return array
     * @throws ContainerException
     * @throws QueryExecutionException
     * @throws InvalidArgumentException
     */
    public function getCouponsData($appointment, $type, $bookingKey = null)
    {
        $couponsData = [];

        /** @var string $break */
        $break = $type === 'email' ? '<p><br></p>' : ($type === 'whatsapp' ? '; ' : PHP_EOL);

        if ($bookingKey !== null) {
            /** @var HelperService $helperService */
            $helperService = $this->container->get('application.helper.service');

            /** @var CouponRepository $couponRepository */
            $couponRepository = $this->container->get('domain.coupon.repository');

            /** @var AbstractCouponApplicationService $couponAS */
            $couponAS = $this->container->get('application.coupon.service');

            /** @var Collection $customerReservations */
            $customerReservations = new Collection();

            $type            = $appointment['type'];
            $customerId      = $type !== Entities::PACKAGE ? $appointment['bookings'][$bookingKey]['customerId'] : $appointment['customer']['id'];
            $couponsCriteria = [
                'notExpired'           => true,
                'notificationInterval' => true,
            ];

            if (!$customerId) {
                return $couponsData;
            }

            switch ($type) {
                case Entities::APPOINTMENT:
                    $couponsCriteria['entityIds'] = [$appointment['serviceId']];

                    $couponsCriteria['entityType'] = Entities::SERVICE;

                    break;

                case Entities::EVENT:
                    $couponsCriteria['entityIds'] = [$appointment['id']];

                    $couponsCriteria['entityType'] = Entities::EVENT;

                    break;

                case Entities::PACKAGE:
                    $couponsCriteria['entityIds'] = [$appointment['id']];

                    $couponsCriteria['entityType'] = Entities::PACKAGE;

                    break;
            }

            /** @var Collection $entityCoupons */
            $entityCoupons = $couponAS->getAllByCriteria($couponsCriteria);

            if (!$entityCoupons->length()) {
                return $couponsData;
            }

            switch ($type) {
                case Entities::APPOINTMENT:
                    /** @var AppointmentRepository $appointmentRepository */
                    $appointmentRepository = $this->container->get('domain.booking.appointment.repository');

                    $customerReservations = $appointmentRepository->getPeriodAppointments(
                        [
                            'customerId'    => $customerId,
                            'skipServices'  => true,
                            'skipProviders' => true,
                            'skipCustomers' => true,
                            'skipPayments'  => true,
                            'skipExtras'    => true,
                            'skipCoupons'   => true,
                            'status'        => BookingStatus::APPROVED,
                            'bookingStatus' => BookingStatus::APPROVED,
                            'services'      => [
                                $appointment['serviceId']
                            ]
                        ]
                    );

                    break;

                case Entities::EVENT:
                    /** @var EventRepository $eventRepository */
                    $eventRepository = $this->container->get('domain.booking.event.repository');

                    /** @var Collection $eventsBookings */
                    $eventsBookings = $eventRepository->getBookingsByCriteria(
                        [
                            'ids'                   => [$appointment['id']],
                            'customerId'            => $customerId,
                            'customerBookingStatus' => BookingStatus::APPROVED,
                            'fetchBookings'         => false,
                            'fetchBookingsTickets'  => false,
                            'fetchBookingsUsers'    => false,
                            'fetchBookingsPayments' => false,
                        ]
                    );

                    /** @var Collection $customerReservations */
                    $customerReservations = new Collection();

                    /** @var Collection $eventBookings */
                    foreach ($eventsBookings->getItems() as $eventBookings) {
                        /** @var Collection $booking */
                        foreach ($eventBookings->getItems() as $bookingId => $booking) {
                            $customerReservations->addItem($booking, $bookingId);
                        }
                    }

                    break;

                case Entities::PACKAGE:
                    /** @var PackageCustomerRepository $packageCustomerRepository */
                    $packageCustomerRepository = $this->container->get('domain.bookable.packageCustomer.repository');

                    $customerReservations = $packageCustomerRepository->getFiltered(
                        [
                            'packages'      => [$appointment['id']],
                            'customerId'    => $customerId,
                            'bookingStatus' => BookingStatus::APPROVED,
                        ]
                    );

                    break;
            }

            foreach (array_diff($couponRepository->getIds(), $entityCoupons->keys()) as $couponId) {
                $couponsData["coupon_{$couponId}"] = '';
            }

            /** @var Coupon $coupon */
            foreach ($entityCoupons->getItems() as $coupon) {
                $sendCoupon = (
                        $customerReservations->length() &&
                        !$coupon->getNotificationRecurring()->getValue() &&
                        $customerReservations->length() === $coupon->getNotificationInterval()->getValue()
                    ) || (
                        $customerReservations->length() &&
                        $coupon->getNotificationRecurring()->getValue() &&
                        $customerReservations->length() % $coupon->getNotificationInterval()->getValue() === 0
                    );

                try {
                    if ($sendCoupon && $couponAS->inspectCoupon($coupon, $customerId, true, true)) {
                        $couponsData["coupon_{$coupon->getId()->getValue()}"] =
                            FrontendStrings::getCommonStrings()['coupon_send_text'] . ' ' .
                            $coupon->getCode()->getValue() . ' ' . $break .
                            ($coupon->getDeduction() && $coupon->getDeduction()->getValue() ?
                                BackendStrings::get('deduction') . ' ' .
                                $helperService->getFormattedPrice($coupon->getDeduction()->getValue()) . ' ' . $break
                                : ''
                            ) .
                            ($coupon->getDiscount() && $coupon->getDiscount()->getValue() ?
                                BackendStrings::get('discount_amount') . ' ' .
                                $coupon->getDiscount()->getValue() . '% ' . $break
                                : '') .
                            ($coupon->getStartDate() && $coupon->getStartDate()->getValue() ?
                                BackendStrings::get('start_date') . ': ' .
                                date_i18n($coupon->getStartDate()->getValue()->format('Y-m-d')) . ' ' : '') .
                            ($coupon->getExpirationDate() && $coupon->getExpirationDate()->getValue() ?
                                BackendStrings::get('expiration_date') . ': ' .
                                date_i18n($coupon->getExpirationDate()->getValue()->format('Y-m-d')) : '');
                    } else {
                        $couponsData["coupon_{$coupon->getId()->getValue()}"] = '';
                    }
                } catch (CouponUnknownException $e) {
                    $couponsData["coupon_{$coupon->getId()->getValue()}"] = '';
                } catch (CouponInvalidException $e) {
                    $couponsData["coupon_{$coupon->getId()->getValue()}"] = '';
                } catch (CouponExpiredException $e) {
                    $couponsData["coupon_{$coupon->getId()->getValue()}"] = '';
                }
            }
        }

        return $couponsData;
    }

    /**
     * @param array $entity
     *
     * @param string $subject
     * @param string $body
     * @param int    $userId
     * @return array
     */
    public function reParseContentForProvider($entity, $subject, $body, $userId)
    {
        $employeeSubject = $subject;

        $employeeBody = $body;

        return [
            'body'    => $employeeBody,
            'subject' => $employeeSubject,
        ];
    }

    /**
     * @param array    $appointment
     * @param int|null $bookingKey
     *
     * @return string|null
     */
    protected function getLocale($appointment, $bookingKey)
    {
        /** @var HelperService $helperService */
        $helperService = $this->container->get('application.helper.service');

        if (!empty($appointment['bookings'][$bookingKey]['customer']['translations'])) {
            return $helperService->getLocaleFromTranslations(
                $appointment['bookings'][$bookingKey]['customer']['translations']
            );
        } elseif (!empty($appointment['bookings'][$bookingKey]['info'])) {
            return $helperService->getLocaleFromBooking(
                $appointment['bookings'][$bookingKey]['info']
            );
        }

        return null;
    }

    /**
     * @param array    $reservation
     * @param int|null $bookingKey
     *
     * @return void
     *
     * @throws ContainerValueNotFoundException
     * @throws NotFoundException
     * @throws QueryExecutionException
     * @throws ContainerException
     * @throws Exception
     */
    protected function setData(&$reservation, $bookingKey = null)
    {
        $info = !empty($reservation['bookings'][$bookingKey]['info']) ?
            json_decode($reservation['bookings'][$bookingKey]['info'], true) : null;

        if (
            $bookingKey !== null &&
            (
                !empty($reservation['bookings'][$bookingKey]['customerId']) ||
                !empty($reservation['bookings'][$bookingKey]['customer']['id'])
            ) &&
            (
                ($info && empty($info['locale'])) ||
                (
                    !$info &&
                    !empty($reservation['bookings'][$bookingKey]['customer']) &&
                    empty($reservation['bookings'][$bookingKey]['customer']['translations'])
                )
            )
        ) {
            /** @var UserRepository $userRepository */
            $userRepository = $this->container->get('domain.users.repository');

            /** @var AbstractUser $customer */
            $customer = $userRepository->getById(
                !empty($reservation['bookings'][$bookingKey]['customerId']) ?
                    $reservation['bookings'][$bookingKey]['customerId'] :
                    $reservation['bookings'][$bookingKey]['customer']['id']
            );

            if ($customer->getTranslations()) {
                if ($info) {
                    $translations = json_decode($customer->getTranslations()->getValue(), true);

                    if ($translations && !empty($translations['defaultLanguage'])) {
                        $info['locale'] = $translations['defaultLanguage'];

                        $reservation['bookings'][$bookingKey]['info'] = json_encode($info);
                    }
                } else {
                    $reservation['bookings'][$bookingKey]['customer']['translations'] =
                        $customer->getTranslations()->getValue();
                }
            }
        }
    }

    /**
     * @param array $appointment
     *
     * @return int|null
     */
    protected function getBookingKeyForEmployee($appointment)
    {
        foreach ($appointment['bookings'] as $booking) {
            if ($booking['isLastBooking'] || $booking['isChangedStatus']) {
                return $booking['id'];
            }
        }

        if (!empty($appointment['isRescheduled']) && $appointment['isRescheduled']) {
            return $appointment['bookings'][0]['id'];
        }

        return null;
    }

    /**
     * Normalize and format datepicker field value.
     * Accepts values like 'YYYY-MM-DD' or ISO 8601 'YYYY-MM-DDTHH:MM:SS(.u)Z'.
     * Returns formatted date string on success, or the original value if parsing fails.
     *
     * @param string $value
     * @param string $dateFormat WordPress date format
     * @return string
     */
    protected function formatDatepickerValue($value, $dateFormat)
    {
        if (empty($value)) {
            return $value;
        }

        $savedDate = (string)$value;
        if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $savedDate, $m)) {
            $savedDate = $m[1];
        } else {
            return $value;
        }

        // Parse/format datepicker values in UTC to preserve the selected calendar date
        // regardless of customer or site timezone offsets.
        $date = DateTime::createFromFormat('!Y-m-d', $savedDate, new \DateTimeZone('UTC'));
        if ($date instanceof DateTime) {
            return wp_date($dateFormat, $date->getTimestamp(), new \DateTimeZone('UTC'));
        }

        return $value;
    }
}
