<?php

/**
 * Copyright 2024-2025 Simply.IN Sp. z o.o.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the EUPL-1.2 or later.
 * You may not use this work except in compliance with the Licence.
 *
 * Copy of the Licence is available at:
 * https://joinup.ec.europa.eu/software/page/eupl
 * It is bundled with this package in the file LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an as is basis,
 * without warranties or conditions of any kind, either express or implied.
 * Check the Licence for the specific language governing permissions
 * and limitations under the License.
 *
 * @author   Simply.IN Sp. z o.o.
 * @copyright 2024-2025 Simply.IN Sp. z o.o.
 * @license   https://joinup.ec.europa.eu/software/page/eupl
 */

declare(strict_types=1);

namespace SimplyIn\Service\OrderSync;

use Configuration;
use SimplyIn\Checkout\CheckoutApiInterface;
use SimplyIn\Checkout\CustomerEmailProviderInterface;
use SimplyIn\Checkout\EmailCacheInterface;
use SimplyIn\Checkout\OrderDataFetcherInterface;
use SimplyIn\Session\SimplyInSessionInterface;
use SimplyInOrder;

if (!defined('_PS_VERSION_')) {
    exit;
}

final class OrderCreationHandler
{
    public function __construct(
        private readonly SimplyInSessionInterface $session,
        private readonly CheckoutApiInterface $api,
        private readonly OrderDataFetcherInterface $orderData,
        private readonly CustomerEmailProviderInterface $customers,
        private readonly EmailCacheInterface $emailCache,
        private readonly OrderSyncQueueInterface $syncQueue,
    ) {
    }

    /**
     * @param array{order?: \Order} $params
     */
    public function handle(array $params): void
    {
        if (!Configuration::get('SIMPLYIN_CONNECTED')) {
            return;
        }

        if (!isset($params['order'])) {
            return;
        }

        /** @var array{order: \Order} $params */
        $isSimplyInPayment = $params['order']->module === 'simplyin';

        $authToken = $this->session->get('simplyin_auth_token', false);
        $newAccountPhone = $this->session->get('new_account_phone', false);

        $hasAuthToken = !empty($authToken);
        $hasNewAccountPhone = !empty($newAccountPhone);

        if ($isSimplyInPayment && ($hasAuthToken || $hasNewAccountPhone)) {
            // The mainline case for an already-authorized shopper: PlaceOrderHandler's own AJAX
            // call already created the order remotely and (best-effort, from the browser) persists
            // this same token to the order - this is the guaranteed, server-side fallback for that
            // exact write, so a shopper whose AJAX call never reached the server (ad blocker, a
            // closed tab, a JS error right after payment) still ends up with a working pay-again
            // paywall. Idempotent with that other write: same token, same order.
            if ($hasAuthToken) {
                $this->persistAuthToken($params['order'], (string) $authToken);
            }

            $this->syncQueue->markSynced($params);

            return;
        }

        if ($hasAuthToken) {
            $this->createOrderWithoutAccount($params, (string) $authToken);

            return;
        }

        if ($hasNewAccountPhone) {
            $this->createOrderAndAccount($params);

            return;
        }

        $this->syncQueue->enqueue($params);
        $this->syncQueue->flush();
    }

    /**
     * @param array{order: \Order} $params
     */
    private function createOrderWithoutAccount(array $params, string $authToken): void
    {
        $order = $params['order'];
        $orderData = $this->orderData->fetch((int) $order->id);
        if (isset($orderData['newOrderData']) && is_array($orderData['newOrderData'])) {
            $orderData['newOrderData']['placedDuringAccountCreation'] = false;
        }
        unset($orderData['newAccountData']);

        $response = $this->api->createOrderWithoutAccount($orderData, $authToken);

        if ($this->isCreateOrderSuccess($response)) {
            $this->syncQueue->markSynced($params);
            $this->persistAuthToken($order, $authToken);
        } else {
            $this->syncQueue->enqueue($params);
        }
    }

    /**
     * @param array{order: \Order} $params
     */
    private function createOrderAndAccount(array $params): void
    {
        $order = $params['order'];
        $email = $this->customers->emailFor((int) $order->id_customer);
        $this->emailCache->clear($email);

        $orderData = $this->orderData->fetch((int) $order->id);
        if (isset($orderData['newOrderData']) && is_array($orderData['newOrderData'])) {
            $orderData['newOrderData']['placedDuringAccountCreation'] = true;
        }

        $response = $this->api->createOrderAndAccount($orderData);

        if (is_array($response) && !empty($response['authToken'])) {
            $this->session->set('simplyin_auth_token', $response['authToken']);
            $this->session->set('simplyin_email', $email);
            $this->session->unset('new_account_phone');
            $this->persistAuthToken($order, (string) $response['authToken']);
        }

        if ($this->isCreateAccountSuccess($response)) {
            $this->syncQueue->markSynced($params);
        } else {
            $this->syncQueue->enqueue($params);
        }
    }

    /**
     * @param null|array<string, mixed> $response
     */
    private function isCreateOrderSuccess(?array $response): bool
    {
        if ($response === null) {
            return false;
        }
        if (!empty($response['ok']) || isset($response['createdOrder']['_id'])) {
            return true;
        }

        return isset($response['message']) && $response['message'] === 'Order already exists';
    }

    /**
     * Binds the authorization to the order it was placed with, not the browser session: the
     * order-confirmation page's pay-again surface needs it and does not reliably run in the session
     * that placed the order.
     */
    private function persistAuthToken(\Order $order, string $authToken): void
    {
        $simplyInOrder = SimplyInOrder::getByIdOrder((int) $order->id);
        if ($simplyInOrder instanceof SimplyInOrder) {
            // Self-healing: a shop that has not yet run the auth_token upgrade must not have this
            // write fail outright on the payment path. Same guard SimplyInPayments takes before its
            // own writes.
            SimplyInOrder::ensureSchema();
            $simplyInOrder->auth_token = $authToken;
            $simplyInOrder->save();
        }
    }

    /**
     * @param null|array<string, mixed> $response
     */
    private function isCreateAccountSuccess(?array $response): bool
    {
        if ($response === null) {
            return false;
        }
        if (isset($response['createdOrder']) || isset($response['ok'])) {
            return true;
        }

        return isset($response['message']) && $response['message'] === 'Order already exists';
    }
}
