<?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 Cart;
use Customer;
use Db;
use Module;
use Order;
use PrestaShopLogger;
use SimplyIn\Api\SimplyInApiClient;
use SimplyIn\BackOffice\ConfigurationKey;
use SimplyIn\BackOffice\ConfigurationRepository;
use SimplyInEmailCache;
use SimplyInOrderSync;

class OrderSyncService
{
    private const SYNC_BATCH_LIMIT = 50;

    public function __construct(
        private readonly SimplyInApiClient $apiClient,
        private readonly ConfigurationRepository $config,
    ) {
    }

    public function recordPendingOrder(Cart $cart, Order $order): void
    {
        $sync = new SimplyInOrderSync();
        $sync->cart_id = (int) $cart->id;
        $sync->order_id = (int) $order->id;
        $sync->is_synced = false;
        $sync->add();
    }

    public function recordSyncedOrder(Cart $cart, Order $order): void
    {
        $sync = new SimplyInOrderSync();
        $sync->cart_id = (int) $cart->id;
        $sync->order_id = (int) $order->id;
        $sync->is_synced = true;
        $sync->date_synced = date('Y-m-d H:i:s');
        $sync->add();
    }

    public function synchronizePending(int $limit = self::SYNC_BATCH_LIMIT): void
    {
        $table = _DB_PREFIX_ . 'simplyin_order_sync';
        $limit = max(1, $limit);

        $rows = Db::getInstance()->executeS(
            'SELECT order_id FROM `' . $table . '` '
            . 'WHERE is_synced = 0 AND ignored = 0 '
            . 'ORDER BY id_simplyin_order_sync ASC LIMIT ' . $limit,
        );

        if (!is_array($rows) || $rows === []) {
            return;
        }

        foreach ($rows as $row) {
            $this->syncOrder((int) $row['order_id']);
        }
    }

    public function refreshPending(): void
    {
        $days = max(0, $this->config->getInt(ConfigurationKey::TRANS_HISTORY_LENGTH_DAY));
        $table = _DB_PREFIX_ . 'simplyin_order_sync';

        $rows = Db::getInstance()->executeS(
            'SELECT o.`id_order` AS order_id, o.`id_cart` AS cart_id '
            . 'FROM `' . _DB_PREFIX_ . 'orders` o '
            . 'WHERE o.`date_add` >= DATE_SUB(NOW(), INTERVAL ' . $days . ' DAY) '
            . 'AND o.id_order NOT IN (SELECT order_id FROM `' . $table . '`)',
        );

        if (is_array($rows)) {
            foreach ($rows as $row) {
                $sync = new SimplyInOrderSync();
                $sync->cart_id = (int) $row['cart_id'];
                $sync->order_id = (int) $row['order_id'];
                $sync->is_synced = false;
                $sync->add();
            }
        }

        $this->markIgnoredByCustomerLookup();
    }

    public function markIgnoredByCustomerLookup(): void
    {
        $table = _DB_PREFIX_ . 'simplyin_order_sync';
        $sql = 'SELECT o.`id_order`, c.`email` '
            . 'FROM `' . _DB_PREFIX_ . 'orders` o '
            . 'JOIN `' . _DB_PREFIX_ . 'customer` c ON o.`id_customer` = c.`id_customer` '
            . 'WHERE o.`id_order` IN ('
            . 'SELECT `order_id` FROM `' . $table . '` '
            . 'WHERE `is_synced` = 0 AND `ignored` = 0)';

        $rows = Db::getInstance()->executeS($sql);
        if (!is_array($rows) || $rows === []) {
            return;
        }

        $ordersByEmail = [];
        foreach ($rows as $row) {
            $ordersByEmail[$row['email']][] = (int) $row['id_order'];
        }

        $existence = SimplyInEmailCache::checkUserListExist(array_keys($ordersByEmail));
        if ($existence === []) {
            return;
        }

        foreach ($existence as $email => $exists) {
            if ($exists) {
                continue;
            }
            foreach ($ordersByEmail[$email] ?? [] as $orderId) {
                $sync = SimplyInOrderSync::getByIdOrder($orderId);
                $sync->ignored = true;
                $sync->date_synced = date('Y-m-d H:i:s');
                $sync->save();
            }
        }
    }

    public function pruneOldSyncRecords(): void
    {
        $days = max(0, $this->config->getInt(ConfigurationKey::TRANS_HISTORY_LENGTH_DAY));
        $table = _DB_PREFIX_ . 'simplyin_order_sync';

        Db::getInstance()->execute(
            'DELETE FROM `' . $table . '` '
            . 'WHERE order_id IN ('
            . 'SELECT id_order FROM `' . _DB_PREFIX_ . 'orders` '
            . 'WHERE date_add < DATE_SUB(NOW(), INTERVAL ' . $days . ' DAY))',
        );
    }

    public function syncOrder(int $orderId): void
    {
        $order = new Order($orderId);
        if (!$order->id) {
            $this->markIgnored($orderId);

            return;
        }

        $customer = new Customer($order->id_customer);
        $email = trim((string) $customer->email);
        if ($email === '') {
            $this->markIgnored($orderId);

            return;
        }

        if (!SimplyInEmailCache::checkUserExist($email)) {
            $this->markIgnored($orderId);

            return;
        }

        $body = SimplyInOrderSync::buildExistingAccountOrderData(
            $order,
            SimplyInOrderSync::getOrderItemsData($order),
            [
                'payment' => $order->payment,
                'module' => $order->module,
            ],
            $this->getPluginVersion(),
            _PS_VERSION_,
            $email,
            SimplyInOrderSync::getParcelMachineIdByOrderId($orderId),
            SimplyInOrderSync::getParcelProviderByOrderId($orderId),
        );

        $response = $this->apiClient->apiCall($body, 'POST', 'checkout/createOrder');

        $alreadyExists = is_array($response) && isset($response['message']) && $response['message'] === 'Order already exists';
        $ok = is_array($response) && (!empty($response['ok']) || isset($response['createdOrder']['_id']));

        if ($alreadyExists || $ok) {
            $this->markSynced($orderId);

            return;
        }

        // Neither branch matched: the row stays is_synced=0 and the cron will retry it forever.
        // That's fine when the backend is genuinely down for one run, but a systemic failure (bad
        // payload, auth problem, permanent backend error) would otherwise stay invisible. Log the
        // failure fact so it can be alerted on; keep it to the order id + response, not the full
        // request payload. allowDuplicate=false so a run of cron retries hitting the exact same
        // failure for the same order collapses to one log row instead of spamming on repeat.
        PrestaShopLogger::addLog(
            sprintf(
                'SimplyIn order sync: checkout/createOrder failed for order #%d - response: %s',
                $orderId,
                is_array($response) ? substr(json_encode($response) ?: '', 0, 500) : var_export($response, true),
            ),
            2,
            null,
            'SimplyIn',
            $orderId,
            false,
        );
    }

    private function markSynced(int $orderId): void
    {
        $sync = SimplyInOrderSync::getByIdOrder($orderId);
        $sync->is_synced = true;
        $sync->date_synced = date('Y-m-d H:i:s');
        $sync->save();
    }

    private function markIgnored(int $orderId): void
    {
        $sync = SimplyInOrderSync::getByIdOrder($orderId);
        $sync->ignored = true;
        $sync->date_synced = date('Y-m-d H:i:s');
        $sync->save();
    }

    private function getPluginVersion(): string
    {
        $module = Module::getInstanceByName('simplyin');
        if ($module === false) {
            return '';
        }

        return $module->version;
    }
}
