<?php


namespace Mtc\Plugins\Wisebee\Classes;

use App\Events\MemberCreatedEvent;
use App\Events\MemberUpdatedEvent;
use App\Events\OrderRefundedEvent;
use Basket;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Mtc\Core\Admin\Menu;
use Mtc\Modules\Members\Classes\Auth;
use Mtc\Modules\Members\Models\Member;
use Mtc\Modules\Members\Models\MembersAddress;
use Mtc\Plugins\Wisebee\Classes\Handlers\ConsultationHandler;
use Mtc\Plugins\Wisebee\Classes\Models\WisebeeConsultation;
use Mtc\Plugins\Wisebee\Classes\Models\WisebeeMember;
use Mtc\Plugins\Wisebee\Classes\Models\WisebeeParticipant;
use Mtc\Plugins\Wisebee\Classes\Models\WisebeeSetting;
use Mtc\Shop\Basket\Item as BasketItem;
use Mtc\Shop\Events\OrderPaidEvent;
use Mtc\Shop\Item;
use Mtc\Shop\Medication;
use Mtc\Shop\Order;
use Wisebee\Resources\Consultation as RemoteConsultation;
use Wisebee\Resources\CustomField as RemoteCustomFieldResource;
use Wisebee\Resources\Payment as RemotePayment;
use Wisebee\Resources\User as RemoteUser;
use Wisebee\Resources\UserAddress as RemoteUserAddress;
use Wisebee\Resources\UserAttribute as RemoteUserAttribute;
use Wisebee\Wisebee;
use Wisebee\WisebeeException;

/**
 * Handles Wisebee actions
 *
 * Class WisebeeManager
 * @package Mtc\Plugins\Wisebee\Classes
 */
class WisebeeManager
{
    private Wisebee $wisebee;

    private Member $member;
    private WisebeeMember $wisebeeMember;

    const WIDGET_BOOKING = 'booking';
    const WIDGET_CONSULTATION = 'consultation';

    const CUSTOM_FIELD_PATIENT_MEDICATIONS = 'Patient Medications';

    const PAYMENT_STATUS_COMPLETE = 'complete';

    const MENU_WISEBEE = 'Wisebee';

    public static array $scripts = [
        self::WIDGET_BOOKING => 'js/widgets/booking.js',
        self::WIDGET_CONSULTATION => 'js/widgets/consultation.js',
    ];

    public function __construct()
    {
        $this->wisebee = new Wisebee(
            config('wisebee.host') . config('wisebee.api_endpoint'),
            config('wisebee.api_key')
        );
    }

    /**
     * Initializes Wisebee
     *
     * @return void
     */
    public static function init(): void
    {
        self::injectAdminMenuItems();
    }

    /**
     * Returns widget snippet with appended auth token
     *
     * @param string $widget Values: booking / consultation
     * @return string
     * @throws Exception
     */
    public function getWidgetSnippet(string $widget): string
    {
        $this->setMember(Auth::getLoggedInMember());
        $this->setWisebeeMember();
        if (empty($this->wisebeeMember)) {
            return '';
        }

        $snippet = '<ocp-' . $widget;

        $snippet .= ' api_key="' . config('wisebee.' . $widget . '_widget_api_key') . '"';
        if (config('wisebee.sync_members')) {

            if (empty($this->wisebeeMember->wisebee_token)) {
                try {
                    $this->loginUser();
                } catch (Exception $e) {
                    return '';
                }
            }
            $snippet .= ' auth_token="' . $this->wisebeeMember->wisebee_token . '" ';
        }
        $snippet .= ' style="width:100%;"></ocp-' . $widget . '>';

        return $snippet;
    }

    /**
     * Logs in user into wisebee. Retrieves auth token to be used for any future requests
     *
     * @throws Exception
     */
    public function loginUser(): void
    {
        try {

            $userResource = new RemoteUser($this->wisebee);
            $userResource->login($this->wisebeeMember->wisebee_id);

            if (empty($userResource->authToken)) {
                throw new WisebeeException($this->wisebee->getErrorMessage());
            }
        } catch (WisebeeException $e) {
            throw new Exception($e->getMessage());
        }
        $this->wisebeeMember->wisebee_token = $userResource->authToken;
        $this->wisebeeMember->save();
    }

    /**
     * @return void
     * @throws Exception
     */
    public function setWisebeeMember(): void
    {
        if (empty($this->member->id)) {
            return;
        }

        if (!empty($this->member->wisebee_member)) {
            $this->wisebeeMember = $this->member->wisebee_member;
            return;
        }

        // Tries to find the user by email in Wisebee
        $remoteUsers = $this->wisebee->list(RemoteUser::class, [
            'email' => $this->member->email,
        ]);

        if (!empty($remoteUsers)) {
            /** @var RemoteUser $userResource */
            $remoteUser = $remoteUsers[0];
        } else {

            // If user is not found, create new user
            $remoteUser = $this->createRemoteUser();
        }

        /** @var WisebeeMember $wisebeeMember */
        $this->wisebeeMember = WisebeeMember::query()
            ->create([
                'wisebee_id' => $remoteUser->id,
                'member_id' => $this->member->id,
            ]);
    }

    /**
     * Logs out Wisebee user
     *
     * @throws Exception
     */
    public function logoutUser(): void
    {
        if (empty($this->wisebeeMember->wisebee_id)) {
            return;
        }
        $remoteUser = new RemoteUser($this->wisebee);
        $remoteUser->logout($this->wisebeeMember->wisebee_id);

        $this->wisebeeMember->wisebee_token = null;
        $this->wisebeeMember->save();
    }

    /**
     * Creates a user in Wisebee platform
     *
     * @return bool|object
     * @throws Exception
     */
    public function createRemoteUser(): object|bool
    {
        $this->member->load('addresses');
        $billingAddress = $this->member->addresses
            ->where('type', 'billing')
            ->first();

        $userData = [
            'firstname' => $billingAddress->firstname,
            'lastname' => $billingAddress->lastname,
            'phone' => $this->member->contact_no,
            'email' => $this->member->email,
            'roles' => ['customer'],
        ];

        if (!$remoteUser = $this->wisebee->create(RemoteUser::class, $userData)) {
            throw new Exception($this->wisebee->getErrorMessage());
        }

        foreach ($this->member->addresses as $address) {
            $addressData = [
                'user_id' => $remoteUser->id,
                'type' => $address->type,
                'address_1' => $address->address1,
                'address_2' => $address->address2,
                'city' => $address->city,
                'postcode' => $address->postcode,
                'country' => $address->country,
                'state' => $address->state,
            ];
            $this->wisebee->create(RemoteUserAddress::class, $addressData);
        }
        $this->member->load('wisebee_member');
        $this->setWisebeeMember();

        $this->createUserMedications();

        return $remoteUser;
    }

    /**
     * Creates user's medications in Wisebee.
     */
    protected function createUserMedications(): void
    {
        if (empty($this->member->medications)) {
            return;
        }

        $medications = json_decode($this->member->medications);

        if (empty($medications)) {
            return;
        }

        // There are some arrays with one empty string...
        if (count($medications) === 1 && $medications[0] == '') {
            return;
        }

        $remoteCustomFields = $this->wisebee->list(RemoteCustomFieldResource::class, [
            'name' => self::CUSTOM_FIELD_PATIENT_MEDICATIONS,
        ]);

        if (empty($remoteCustomFields)) {
            $remoteCustomField = $this->wisebee->create(RemoteCustomFieldResource::class, [
                'name' => self::CUSTOM_FIELD_PATIENT_MEDICATIONS,
                'type' => 'data_source',
                'variations' => Medication::query()
                    ->get()
                    ->pluck('name')
                    ->toArray(),
                'roles' => [
                    'customer',
                ],
                'is_mandatory' => false,
            ]);
        } else {
            $remoteCustomField = $remoteCustomFields[0];
        }

        $this->wisebee->create(RemoteUserAttribute::class, [
            'user_id' => $this->wisebeeMember->wisebee_id,
            'custom_field_id' => $remoteCustomField->id,
            'name' => self::CUSTOM_FIELD_PATIENT_MEDICATIONS,
            'value' => json_encode($medications),
        ]);
    }

    /**
     * Adds consultation to basket
     *
     * @param WisebeeConsultation $wisebeeConsultation
     */
    public function addToBasket(WisebeeConsultation $wisebeeConsultation): void
    {
        $customerParticipant = ConsultationHandler::getCustomerParticipant($wisebeeConsultation);

        if (empty($customerParticipant)) {
            return;
        }
        /** @var WisebeeMember $wisebeeMember */
        $wisebeeMember = WisebeeMember::query()
            ->where('wisebee_id', $customerParticipant->wisebee_user_id)
            ->first();

        if (empty($wisebeeMember)) {
            return;
        }

        $this->setMember($wisebeeMember->member);

        if (!empty($this->member->basket_id)) {
            $basketID = $this->member->basket_id;
        } else {
            /** @var \Mtc\Shop\Basket $memberBasket */
            $memberBasket = \Mtc\Shop\Basket::query()
                ->create([
                    'member' => $this->member->id,
                ]);
            $basketID = $memberBasket->id;
            $this->member->basket_id = $basketID;
            $this->member->save();
        }

        $basket = new Basket($basketID);
        $basket->setDefaults($this->member);
        $basket->set_expiry_time();
        $basket->Go_Basket();

        $item = Item::query()
            ->create([
                'name' => 'Online Consultation',
                'price' => $wisebeeConsultation->cost,
                'price_exvat' => $wisebeeConsultation->cost,
                'hidden' => 1,
                'product_type' => Item::TYPE_CONSULTATION,
                'stock' => 1,
                'epos_code' => $wisebeeConsultation->public_key,
            ]);

        $timeFrom = new Carbon($wisebeeConsultation->time_from);
        $timeTo = (clone $timeFrom)->addMinutes($wisebeeConsultation->length);
        $description = $timeFrom->format('D M j')
            . '<br />' . $timeFrom->format('H:i') . ' - ' . $timeTo->format('H:i')
            . '<br />' . $wisebeeConsultation->length . ' minutes '
            . '<br />' . $wisebeeConsultation->specialist;

        // Add item to basket
        BasketItem::query()
            ->create([
                'basket_id' => $basket->id,
                'item_id' => $item->id,
                'quantity' => 1,
                'size' => $description,
                'size_id' => 0,
                'PLU' => '',
            ]);

        $wisebeeConsultation->added_to_basket = true;
        $wisebeeConsultation->item_id = $item->id;
        $wisebeeConsultation->save();
    }

    /**
     * Deletes consultation on basket item remove
     * Makes API request to Wisebee, so that the consultation is deleted there as well
     *
     * @param $basketItemID
     */
    public function deleteBasketConsultation($basketItemID): void
    {
        if (!$basketItem = BasketItem::query()->find($basketItemID)) {
            return;
        }

        $item = Item::query()
            ->whereHas('wisebee_consultation')
            ->where('id', '=', $basketItem->item_id)
            ->first();

        if (empty($item)) {
            return;
        }

        try {

            // Make sure that only temporary consultations get deleted
            if ($item->wisebee_consultation->status !== WisebeeConsultation::STATUS_TEMPORARY) {
                return;
            }

            $remoteConsultationID = $item->wisebee_consultation->wisebee_id;
            // Remove consultation and item
            $item->wisebee_consultation->delete();
            $item->delete();
            $this->wisebee->delete(RemoteConsultation::class, $remoteConsultationID);
        } catch (Exception $exception) {
            error_log($exception->getMessage());
        }
    }

    /**
     * Builds a request matrix and submits it to Wisebee library for an API request
     *
     * @param OrderPaidEvent $orderPaidEvent
     */
    public function processOrderPaid(OrderPaidEvent $orderPaidEvent): void
    {
        $order = $orderPaidEvent->getOrder();
        $paymentMethod = $orderPaidEvent->getPaymentType();

        $orderItems = $order->getItems();
        foreach ($orderItems as $orderItem) {
            $item = Item::query()
                ->find($orderItem['item_id']);

            /** @var WisebeeConsultation $wisebeeConsultation */
            $wisebeeConsultation = $item->wisebee_consultation;

            if ($item->product_type !== Item::TYPE_CONSULTATION || empty($wisebeeConsultation)) {
                continue;
            }

            $customerParticipant = ConsultationHandler::getCustomerParticipant($wisebeeConsultation);

            if (empty($customerParticipant)) {
                continue;
            }

            $paymentData = [
                'customer_id' => $customerParticipant->wisebee_user_id,
                'consultation_id' => $wisebeeConsultation->wisebee_id,
                'method' => $paymentMethod ?: 'custom',
                'amount' => $orderItem['item_price'],
                'status' => self::PAYMENT_STATUS_COMPLETE,
                'reference' => $order->getOrderRef(),
            ];

            $this->wisebee->create(RemotePayment::class, $paymentData);
            $this->wisebee->update(
                RemoteConsultation::class,
                $wisebeeConsultation->wisebee_id,
                [
                    'status' => WisebeeConsultation::STATUS_PENDING,
                ]
            );

            $wisebeeConsultation->status = WisebeeConsultation::STATUS_PENDING;
            $wisebeeConsultation->save();

            $wisebeeConsultation->participants()
                ->update([
                    'status' => WisebeeParticipant::STATUS_ACCEPTED,
                ]);
        }
    }

    /**
     * Created a refund transaction
     *
     * @param OrderRefundedEvent $orderRefundedEvent
     */
    public function processOrderRefunded(OrderRefundedEvent $orderRefundedEvent): void
    {
        $order = $orderRefundedEvent->getOrder();
        $amount = $orderRefundedEvent->getAmount();

        $orderItems = $order->getItems();
        foreach ($orderItems as $orderItem) {
            $item = Item::query()
                ->find($orderItem['item_id']);

            $wisebeeConsultation = $item->wisebee_consultation;

            if ($item->product_type !== Item::TYPE_CONSULTATION || empty($wisebeeConsultation)) {
                continue;
            }

            $wisebeePayments = $this->wisebee->list(RemotePayment::class, [
                'consultation_id' => $wisebeeConsultation->wisebee_id,
            ]);
            if (empty($wisebeePayments)) {
                continue;
            }

            $wisebeePayment = new RemotePayment($this->wisebee);
            $wisebeePayment->refund($wisebeePayments[0]->id, [
                'amount' => $amount,
                'remote_id' => '',
            ]);
        }
    }


    /**
     * Injects menu items in admin menu
     *
     * @return void
     */
    protected static function injectAdminMenuItems(): void
    {
        if (Menu::query()->where('title', self::MENU_WISEBEE)->exists()) {
            return;
        }

        $wisebeeMenu = Menu::query()
            ->create([
                'sub_id' => '0',
                'title' => self::MENU_WISEBEE,
                'icon' => '',
                'order' => '1',
            ]);

        $subItems = [
            [
                'sub_id' => $wisebeeMenu->id,
                'title' => 'Consultations',
                'path' => '/shop/admin/orders/manage.orders.php?product_type=consultation',
                'activePath' => '/shop/admin/orders/manage.orders.php?product_type=consultation',
                'icon' => 'fa fa-user-md',
                'order' => '1',
            ],
            [
                'sub_id' => $wisebeeMenu->id,
                'title' => 'Configuration',
                'path' => route('wisebee-admin-configuration', [], false),
                'activePath' => route('wisebee-admin-configuration', [], false),
                'icon' => 'fa fa-cog',
                'order' => '2',
            ]
        ];

        foreach ($subItems as $subItem) {
            Menu::query()
                ->create($subItem);
        }
    }

    /**
     * Gets Wisebee scripts
     *
     * @param string $type
     * @return string
     */
    public static function getWisebeeScript(string $type): string
    {
        return '"<script defer src="' . config('wisebee.host') . self::$scripts[$type] . '"></script>"';
    }

    /**
     * Getter for Wisebee
     *
     * @return Wisebee
     */
    public function wisebee(): Wisebee
    {
        return $this->wisebee;
    }

    /**
     * Getter for Wisebee
     *
     * @return WisebeeMember|null
     */
    public function wisebeeMember(): WisebeeMember|null
    {
        return $this->wisebeeMember;
    }

    /**
     * Member setter
     *
     * @param Member|null $member
     * @return void
     */
    public function setMember(Member|null $member): void
    {
        $this->member = $member;
    }

    /**
     * Amend basket contents based on rules
     *
     * @param Basket $basket
     * @return void
     */
    public static function processBasket(Basket $basket): void
    {

        if (!WisebeeSetting::getSetting(WisebeeSetting::SETTING_APPLY_TO_CATEGORIES)) {
            return;
        }

        $basket->hasConsultation = self::basketHasConsultation($basket);

        $basket->hasMandatoryConsultationItems = self::basketHasMandatoryConsultationItems($basket);

        if (!$basket->hasMandatoryConsultationItems) {
            return;
        }

        foreach ($basket->items as $key => $basketItem) {
            if ($basketItem['product_type'] === Item::TYPE_CONSULTATION) {
                $basket->items[$key]['can_remove'] = false;
            }
        }
    }

    /**
     * Amend basket contents based on rules
     *
     * @param Basket $basket
     * @return void
     */
    public static function processBasketItems(Basket $basket): void
    {
        if (self::freeConsultationCanBeGiven($basket)) {
            // Deduct amount as coupon
            foreach ($basket->items as $basketItem) {
                if ($basketItem['product_type'] === Item::TYPE_CONSULTATION) {
                    $basket->coupon_deduct += $basketItem['item_price'];
                }
            }
        }
    }

    /**
     * If there are items in the basket that require consultation and
     * the settings state that the consultation is free in this situation
     *
     * @param Basket $basket
     * @return bool
     */
    public static function freeConsultationCanBeGiven(Basket $basket): bool
    {
        if (!WisebeeSetting::getSetting(WisebeeSetting::SETTING_APPLY_TO_CATEGORIES)) {
            return false;
        }

        if (!WisebeeSetting::getSetting(WisebeeSetting::SETTING_CONSULTATION_WITH_PRODUCT_FREE)) {
            return false;
        }

        return self::basketHasMandatoryConsultationItems($basket);
    }

    /**
     * Any actions that need to be performed on an order
     *
     * @param Order $order
     * @param Basket $basket
     * @return void
     */
    public static function processNewOrder(Order $order, Basket $basket): void
    {
        if (self::freeConsultationCanBeGiven($basket)) {

            // Any consultation creates a discount on order
            foreach ($basket->items as $basketItem) {
                if ($basketItem['product_type'] === Item::TYPE_CONSULTATION) {
                    $order->discounts()->create([
                        'discount_amount' => $basketItem['item_price'],
                        'discount_type' => 'Free consultation',
                        'discount_id' => 0,
                        'discount_name' => 'Free consultation',
                        'payment_type' => '',
                    ]);
                }
            }
        }
    }

    /**
     * Whether there's a consultation in the basket
     *
     * @param Basket $basket
     * @return bool
     */
    public static function basketHasConsultation(Basket $basket): bool
    {
        $basketItems = collect($basket->items);
        return $basketItems->pluck('product_type')
            ->contains(Item::TYPE_CONSULTATION);
    }

    /**
     * Whether any items in the basket require consultation
     *
     * @param Basket $basket
     * @return bool
     */
    public static function basketHasMandatoryConsultationItems(Basket $basket): bool
    {
        $basketItems = collect($basket->items);
        $itemIds = $basketItems->pluck('item_id');
        return Item::query()
            ->whereIn('id', $itemIds)
            ->whereHas('categories', function (Builder $query) {
                $query->whereHas('wisebee_categories', function (Builder $query) {
                    $query->where('consultation_required', true);
                });
            })
            ->exists();
    }

    /**
     * Logs in user from the hash in URL
     *
     * @param $hash
     * @return void
     */
    public function loginUserFromHash($hash): void
    {
        $member = Auth::getLoggedInMember();
        if (!empty($member->id)) {
            return;
        }
        $participant = WisebeeParticipant::query()
            ->where('hash', $hash)
            ->whereNotNull('hash')
            ->first();
        if (empty($participant)) {
            return;
        }
        $wisebeeMember = WisebeeMember::query()
            ->where('wisebee_id', $participant->wisebee_user_id)
            ->first();
        if (empty($wisebeeMember)) {
            return;
        }
        /** @var Member $member */
        $member = Member::query()
            ->find($wisebeeMember->member_id);
        if (empty($member)) {
            return;
        }
        Auth::login($member);
    }

    /**
     * When paying for consultation, add to basket and return redirect URL
     *
     * @param string $hash
     * @return string|null
     */
    public function getPaymentRedirect(string $hash): ?string
    {
        // Get customer participant in status 'pending'
        /** @var WisebeeParticipant $wisebeeParticipant */
        $wisebeeParticipant = WisebeeParticipant::query()
            ->where('hash', $hash)
            ->where('status', WisebeeParticipant::STATUS_PENDING)
            ->first();
        if (empty($wisebeeParticipant)) {
            return null;
        }
        /** @var WisebeeConsultation $wisebeeConsultation */
        $wisebeeConsultation = WisebeeConsultation::query()
            ->where('wisebee_id', $wisebeeParticipant->wisebee_consultation_id)
            ->first();
        if (empty($wisebeeConsultation)) {
            return null;
        }

        $this->addToBasket($wisebeeConsultation);

        return '/shop/checkout/basket_overview.php';
    }

    /**
     * When a member is created / updated, sync their details with Wisebee
     * When updating, function will check if the member data has changed
     * and only update the details if they have
     *
     * @param MemberCreatedEvent|MemberUpdatedEvent $event
     * @return void
     * @throws Exception
     */
    public function syncMember(MemberCreatedEvent|MemberUpdatedEvent $event): void
    {
        $member = $event->getMember();

        if (empty($member->wisebee_member) || $event instanceof MemberCreatedEvent) {
            $this->setMember($member);
            $this->setWisebeeMember();
            return;
        }

        $oldMember = $event->getOldMember();
        if (!$this->memberDetailsChanged($member, $oldMember)) {
            return;
        }

        $billingAddress = $member->addresses
            ->where('type', MembersAddress::TYPE_BILLING)
            ->first();

        $this->wisebee->update(
            RemoteUser::class,
            $member->wisebee_member->wisebee_id,
            [
                'firstname' => $billingAddress->firstname,
                'lastname' => $billingAddress->lastname,
                'phone' => $member->contact_no,
                'email' => $member->email,
            ]
        );

        $remoteAddresses = $this->wisebee->list(RemoteUserAddress::class, [
            'user_id' => $member->wisebee_member->wisebee_id,
        ]);

        foreach ($member->addresses as $address) {
            $addressData = [
                'user_id' => $member->wisebee_member->wisebee_id,
                'type' => $address->type,
                'address_1' => $address->address1,
                'address_2' => $address->address2,
                'city' => $address->city,
                'postcode' => $address->postcode,
                'country' => $address->country,
                'state' => $address->state,
            ];
            $remoteAddress = null;
            if (!empty($remoteAddresses)) {
                $remoteAddress = collect($remoteAddresses)
                    ->where('type', $address->type)
                    ->first();
            }
            if (empty($remoteAddress)) {
                $this->wisebee->create(RemoteUserAddress::class, $addressData);
            } else {
                $this->wisebee->update(RemoteUserAddress::class, $remoteAddress->id, $addressData);
            }
        }
    }

    /**
     * Checks if the member's details or billing address have changed.
     *
     * @param Member $member The current member instance with potentially updated details.
     * @param Member $oldMember The previous member instance with the original details.
     *
     * @return bool Returns true if any member details or the billing address have changed; otherwise, false.
     */
    protected function memberDetailsChanged(Member $member, Member $oldMember): bool
    {
        $memberFields = [
            'email',
            'contact_no',
        ];

        $addressFields = [
            'firstname',
            'lastname',
            'address_1',
            'address_2',
            'city',
            'state',
            'postcode',
            'country',
        ];

        if (array_any($memberFields, fn($field) => $member->$field !== $oldMember->$field)) {
            return true;
        }
        $oldAddress = $oldMember->addresses
            ->where('type', MembersAddress::TYPE_BILLING)
            ->first();
        $newAddress = $member->addresses
            ->where('type', MembersAddress::TYPE_BILLING)
            ->first();
        if (empty($oldAddress) && !empty($newAddress)) {
            return false;
        }
        return array_any($addressFields, fn($field) => $newAddress->$field !== $oldAddress->$field);
    }
}
