<?php

/**
 * This file contains QUI\ERP\Process
 */

namespace QUI\ERP;

use QUI;

use function array_filter;
use function array_map;
use function array_merge;
use function class_exists;
use function count;
use function is_callable;
use function strtotime;

/**
 * Class Process
 * - represents a complete erp process
 * - Vorgangsnummer
 *
 * @package QUI\ERP
 */
class Process
{
    /**
     * this date determines when the global process ids start to work.
     * also the relationships.
     */
    const PROCESS_ACTIVE_DATE = '2024-08-01 00:00:00';

    /**
     * @var string
     */
    protected string $processId;

    /**
     * @var null|array
     */
    protected ?array $transactions = null;

    /**
     * @var null|QUI\ERP\Comments
     */
    protected ?Comments $History = null;

    /**
     * Process constructor.
     *
     * @param string $processId - the global process id
     */
    public function __construct(string $processId)
    {
        $this->processId = $processId;
    }

    /**
     * Return the db table name
     *
     * @return string
     */
    protected function table(): string
    {
        return QUI::getDBTableName('process');
    }

    public function getUUID(): string
    {
        return $this->processId;
    }

    /**
     * Return all entities which are connected to this process
     *
     * @return ErpEntityInterface[]
     */
    public function getEntities(): array
    {
        $entities = array_merge(
            $this->getInvoices(),
            $this->getOrders(),
            $this->getOffers(),
            $this->getBookings(),
            $this->getPurchasing(),
            $this->getSalesOrders()
        );

        return array_filter($entities);
    }

    /**
     * This method is designed to organize and group ERP transaction entities that implement the ErpTransactionsInterface.
     * It filters entities such as invoices, invoice drafts, orders,
     * and other related items to identify and group together entities that are interconnected.
     *
     * For instance, if an order generates an invoice, this method will group them together.
     * The primary function is to categorize these ERP entities based on their relationships and associations,
     * facilitating easier management and retrieval of related transactional data within the ERP system.
     *
     * @param callable|null $filterEntityTypes - own filter function
     * @return array
     */
    public function getGroupedRelatedTransactionEntities(?callable $filterEntityTypes = null): array
    {
        $entities = $this->getEntities();
        $entities = array_filter($entities, function ($obj) {
            return $obj instanceof ErpTransactionsInterface;
        });

        if (is_callable($filterEntityTypes)) {
            $entities = array_filter($entities, $filterEntityTypes);
        }

        $groups = [];

        // invoices into the groups
        if (
            class_exists('QUI\ERP\Accounting\Invoice\Invoice')
            && class_exists('QUI\ERP\Accounting\Invoice\InvoiceTemporary')
        ) {
            foreach ($entities as $Entity) {
                if (
                    !($Entity instanceof QUI\ERP\Accounting\Invoice\Invoice)
                    && !($Entity instanceof QUI\ERP\Accounting\Invoice\InvoiceTemporary)
                ) {
                    continue;
                }

                $uuid = $Entity->getUUID();

                $groups[$uuid][] = $Entity;

                if (class_exists('QUI\ERP\Order\Handler') && $Entity->getAttribute('order_id')) {
                    try {
                        $groups[$uuid][] = QUI\ERP\Order\Handler::getInstance()->get(
                            $Entity->getAttribute('order_id')
                        );
                    } catch (QUI\Exception) {
                    }
                }

                if (class_exists('QUI\ERP\SalesOrders\Handler')) {
                    $salesOrder = $Entity->getPaymentData('salesOrder');

                    if ($salesOrder) {
                        try {
                            $groups[$uuid][] = QUI\ERP\SalesOrders\Handler::getSalesOrder($salesOrder['hash']);
                        } catch (QUI\Exception) {
                        }
                    }
                }
            }
        }

        if (empty($groups)) {
            if (class_exists('QUI\ERP\Order\Order')) {
                foreach ($entities as $Entity) {
                    if (!($Entity instanceof QUI\ERP\Order\Order)) {
                        continue;
                    }

                    $uuid = $Entity->getUUID();

                    $groups[$uuid][] = $Entity;

                    if (class_exists('QUI\ERP\SalesOrders\Handler')) {
                        $salesOrder = $Entity->getPaymentData('salesOrder');

                        if ($salesOrder) {
                            try {
                                $groups[$uuid][] = QUI\ERP\SalesOrders\Handler::getSalesOrder($salesOrder['hash']);
                            } catch (QUI\Exception) {
                            }
                        }
                    }
                }
            }
        }

        if (empty($groups)) {
            if (class_exists('QUI\ERP\SalesOrders\SalesOrder')) {
                foreach ($entities as $Entity) {
                    if (!($Entity instanceof QUI\ERP\SalesOrders\SalesOrder)) {
                        continue;
                    }

                    $uuid = $Entity->getUUID();
                    $groups[$uuid][] = $Entity;
                }
            }
        }


        // not group
        $notGroup = [];
        $isInGroups = function (ErpEntityInterface $Entity) use ($groups) {
            foreach ($groups as $group) {
                foreach ($group as $EntityInstance) {
                    if ($Entity->getUUID() === $EntityInstance->getUUID()) {
                        return true;
                    }
                }
            }

            return false;
        };

        foreach ($entities as $Entity) {
            if (!$isInGroups($Entity)) {
                $notGroup[] = $Entity;
            }
        }

        // resulting
        $entitiesArray = array_map(function ($Entity) {
            return $Entity->toArray();
        }, $entities);

        $notGroup = array_map(function ($Entity) {
            return $Entity->toArray();
        }, $notGroup);

        $grouped = [];

        foreach ($groups as $key => $entries) {
            foreach ($entries as $entity) {
                $grouped[$key][] = $entity->toArray();
            }
        }

        return [
            'entities' => $entitiesArray,
            'grouped' => $grouped,
            'notGroup' => $notGroup
        ];
    }

    //region messages

    /**
     * Add a comment to the history for the complete process
     *
     * @param string $message
     * @param bool|int $time - optional, unix timestamp
     */
    public function addHistory(string $message, bool | int $time = false): void
    {
        $this->getHistory()->addComment($message, $time);

        try {
            QUI::getDataBase()->update(
                $this->table(),
                ['history' => $this->getHistory()->toJSON()],
                ['id' => $this->processId]
            );
        } catch (\QUI\Exception $Exception) {
            QUI\System\Log::addError($Exception->getMessage());
        }
    }

    /**
     * Return the history of the process
     * This history only contains the process history
     *
     * If you want the complete history of all process objects, use getCompleteHistory()
     *
     * @return QUI\ERP\Comments
     */
    public function getHistory(): Comments
    {
        if ($this->History === null) {
            $history = '';

            try {
                $result = QUI::getDataBase()->fetch([
                    'from' => $this->table(),
                    'where' => [
                        'id' => $this->processId
                    ],
                    'limit' => 1
                ]);

                if (isset($result[0]['history'])) {
                    $history = $result[0]['history'];
                } elseif (!isset($result[0])) {
                    QUI::getDataBase()->insert($this->table(), [
                        'id' => $this->processId
                    ]);
                }
            } catch (\QUI\Exception $Exception) {
                QUI\System\Log::addError($Exception->getMessage());
            }

            $this->History = QUI\ERP\Comments::unserialize($history);
        }

        return $this->History;
    }

    /**
     * Return a complete history of all process objects
     * invoices and orders
     *
     * @return Comments
     */
    public function getCompleteHistory(): Comments
    {
        $History = $this->getHistory();

        $this->parseBookings($History);
        $this->parseInvoices($History);
        $this->parseOffers($History);
        $this->parseOrders($History);
        $this->parsePurchasing($History);
        $this->parseSalesOrders($History);
        $this->parseTransactions($History);

        try {
            QUI::getEvents()->fireEvent('quiqqerErpGetCompleteHistory', [$this, $this->processId]);
        } catch (\Exception $exception) {
            QUI\System\Log::addError($exception->getMessage());
        }

        try {
            QUI::getEvents()->fireEvent('quiqqerErpProcessHistory', [$this, $this->processId]);
        } catch (\Exception $exception) {
            QUI\System\Log::addError($exception->getMessage());
        }

        // filter comments
        /**
         * this date determines when the global process ids start to work.
         * also the relationships.
         */
        $processDate = strtotime(self::PROCESS_ACTIVE_DATE);
        $comments = $History->toArray();

        $comments = array_filter($comments, function ($comment) use ($processDate) {
            $createDate = $comment['time'];

            if ($createDate > $processDate) {
                return true;
            }

            return false;
        });

        $History = QUI\ERP\Comments::unserialize($comments);

        if ($History->isEmpty()) {
            $History->addComment(
                QUI::getLocale()->get('quiqqer/erp', 'process.history.empty.info'),
                strtotime(self::PROCESS_ACTIVE_DATE),
                'quiqqer/erp',
                'fa fa-info'
            );
        }

        return $History;
    }

    //endregion

    //region invoice

    protected function parseInvoices(Comments $History): void
    {
        $invoices = $this->getInvoices();

        foreach ($invoices as $Invoice) {
            $History->addComment(
                QUI::getLocale()->get('quiqqer/erp', 'process.history.invoice.created', [
                    'hash' => $Invoice->getPrefixedNumber()
                ]),
                strtotime($Invoice->getAttribute('date')),
                'quiqqer/invoice',
                'fa fa-file-text-o',
                false,
                $Invoice->getUUID()
            );

            $history = $Invoice->getHistory()->toArray();

            foreach ($history as $entry) {
                if (empty($entry['source'])) {
                    $entry['source'] = 'quiqqer/invoice';
                }

                if (empty($entry['sourceIcon'])) {
                    $entry['sourceIcon'] = 'fa fa-file-text-o';
                }

                $History->addComment(
                    $entry['message'],
                    $entry['time'],
                    $entry['source'],
                    $entry['sourceIcon'],
                    $entry['id'],
                    $Invoice->getUUID()
                );
            }
        }
    }

    /**
     * Return if the process has invoices or not
     *
     * @return bool
     */
    public function hasInvoice(): bool
    {
        $invoices = $this->getInvoices();

        foreach ($invoices as $Invoice) {
            if ($Invoice instanceof QUI\ERP\Accounting\Invoice\Invoice) {
                return true;
            }
        }

        return false;
    }

    /**
     * Return if the process has temporary invoices or not
     *
     * @return bool
     */
    public function hasTemporaryInvoice(): bool
    {
        $invoices = $this->getInvoices();

        foreach ($invoices as $Invoice) {
            if ($Invoice instanceof QUI\ERP\Accounting\Invoice\InvoiceTemporary) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return Accounting\Invoice\Invoice[]|Accounting\Invoice\InvoiceTemporary[]
     */
    public function getInvoices(): array
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/invoice')) {
            return [];
        }

        try {
            return QUI\ERP\Accounting\Invoice\Handler::getInstance()->getInvoicesByGlobalProcessId(
                $this->processId
            );
        } catch (\QUI\Exception) {
            return [];
        }
    }

    //endregion

    //region order

    protected function parseOrders(Comments $History): void
    {
        // orders
        $orders = $this->getOrders();

        foreach ($orders as $Order) {
            $history = $Order->getHistory()->toArray();
            $hasCreateMessage = false;
            $createMessage = QUI::getLocale()->get('quiqqer/erp', 'process.history.order.created', [
                'hash' => $Order->getPrefixedNumber()
            ]);

            foreach ($history as $entry) {
                if ($entry['message'] === $createMessage) {
                    $hasCreateMessage = true;
                    break;
                }
            }

            if ($hasCreateMessage === false) {
                $History->addComment(
                    $createMessage,
                    strtotime($Order->getCreateDate()),
                    'quiqqer/order',
                    'fa fa-shopping-basket'
                );
            }

            foreach ($history as $entry) {
                if (empty($entry['source'])) {
                    $entry['source'] = 'quiqqer/order';
                }

                if (empty($entry['sourceIcon'])) {
                    $entry['sourceIcon'] = 'fa fa-shopping-basket';
                }

                $History->addComment(
                    $entry['message'],
                    $entry['time'],
                    $entry['source'],
                    $entry['sourceIcon'],
                    $entry['id'],
                    $Order->getUUID()
                );
            }
        }
    }

    /**
     * @return bool
     */
    public function hasOrder(): bool
    {
        return !($this->getOrder() === null);
    }

    /**
     * Return the first order of the process
     *
     * @return null|Order\Order|Order\OrderInProcess
     */
    public function getOrder(): Order\OrderInProcess | Order\Order | null
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/order')) {
            return null;
        }

        $OrderHandler = QUI\ERP\Order\Handler::getInstance();

        try {
            return $OrderHandler->getOrderByGlobalProcessId($this->processId);
        } catch (QUI\Exception $Exception) {
            QUI\System\Log::writeDebugException($Exception);
        }

        try {
            return $OrderHandler->getOrderByHash($this->processId);
        } catch (QUI\Exception $Exception) {
            QUI\System\Log::writeDebugException($Exception);
        }

        return null;
    }

    /**
     * Return all orders from the process
     *
     * @return array
     */
    public function getOrders(): array
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/order')) {
            return [];
        }

        try {
            return QUI\ERP\Order\Handler::getInstance()->getOrdersByGlobalProcessId($this->processId);
        } catch (QUI\Exception) {
            return [];
        }
    }

    //endregion

    //region offers
    protected function parseOffers(Comments $History): void
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/offers')) {
            return;
        }

        // orders
        $offers = $this->getOffers();

        foreach ($offers as $Offer) {
            $History->addComment(
                QUI::getLocale()->get('quiqqer/erp', 'process.history.offer.created', [
                    'hash' => $Offer->getHash()
                ]),
                strtotime($Offer->getAttribute('date')),
                'quiqqer/offer',
                'fa fa-regular fa-handshake',
                false,
                $Offer->getHash()
            );

            $history = $Offer->getHistory()->toArray();

            foreach ($history as $entry) {
                if (empty($entry['source'])) {
                    $entry['source'] = 'quiqqer/offer';
                }

                if (empty($entry['sourceIcon'])) {
                    $entry['sourceIcon'] = 'fa fa-regular fa-handshake';
                }

                $History->addComment(
                    $entry['message'],
                    $entry['time'],
                    $entry['source'],
                    $entry['sourceIcon'],
                    $entry['id'],
                    $Offer->getHash()
                );
            }
        }
    }

    /**
     * @return QUI\ERP\Accounting\Offers\Offer[]
     */
    public function getOffers(): array
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/offers')) {
            return [];
        }

        try {
            $offers = QUI::getDatabase()->fetch([
                'select' => 'id,hash,global_process_id,date',
                'from' => QUI\ERP\Accounting\Offers\Handler::getInstance()->offersTable(),
                'where_or' => [
                    'global_process_id' => $this->processId,
                    'hash' => $this->processId
                ]
            ]);
        } catch (\Exception) {
            $offers = [];
        }

        $result = [];
        $Offers = QUI\ERP\Accounting\Offers\Handler::getInstance();

        foreach ($offers as $offer) {
            try {
                $result[] = $Offers->getOffer($offer['id']);
            } catch (\Exception) {
            }
        }

        // temporary
        try {
            $temporaryOffers = QUI::getDatabase()->fetch([
                'select' => 'id,hash,global_process_id,date',
                'from' => QUI\ERP\Accounting\Offers\Handler::getInstance()->temporaryOffersTable(),
                'where_or' => [
                    'global_process_id' => $this->processId,
                    'hash' => $this->processId
                ]
            ]);
        } catch (\Exception) {
            $temporaryOffers = [];
        }

        foreach ($temporaryOffers as $temporaryOffer) {
            try {
                $result[] = $Offers->getTemporaryOffer($temporaryOffer['id']);
            } catch (\Exception) {
            }
        }

        return $result;
    }

    //endregion

    //region booking
    protected function parseBookings(Comments $History): void
    {
        // orders
        $bookings = $this->getBookings();

        foreach ($bookings as $Booking) {
            $History->addComment(
                QUI::getLocale()->get('quiqqer/erp', 'process.history.booking.created', [
                    'hash' => $Booking->getPrefixedNumber()
                ]),
                $Booking->getCreateDate()->getTimestamp(),
                'quiqqer/booking',
                'fa fa-ticket',
                false,
                $Booking->getUuid()
            );

            $history = $Booking->getHistory()->toArray();

            foreach ($history as $entry) {
                if (empty($entry['source'])) {
                    $entry['source'] = 'quiqqer/booking';
                }

                if (empty($entry['sourceIcon'])) {
                    $entry['sourceIcon'] = 'fa fa-ticket';
                }

                $History->addComment(
                    $entry['message'],
                    $entry['time'],
                    $entry['source'],
                    $entry['sourceIcon'],
                    $entry['id'],
                    $Booking->getUuid()
                );
            }
        }
    }

    /**
     * @return array
     */
    public function getBookings(): array
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/booking')) {
            return [];
        }

        if (!class_exists('QUI\ERP\Booking\Repository\BookingRepository')) {
            return [];
        }

        if (!class_exists('QUI\ERP\Booking\Table')) {
            return [];
        }

        try {
            $bookings = QUI::getDatabase()->fetch([
                'select' => 'uuid,globalProcessId,createDate',
                'from' => QUI\ERP\Booking\Table::BOOKINGS->tableName(),
                'where_or' => [
                    'globalProcessId' => $this->processId,
                    'uuid' => $this->processId
                ]
            ]);
        } catch (\Exception) {
            return [];
        }

        $result = [];
        $BookingRepository = new QUI\ERP\Booking\Repository\BookingRepository();

        foreach ($bookings as $booking) {
            try {
                $result[] = $BookingRepository->getByUuid($booking['uuid']);
            } catch (\Exception) {
            }
        }

        return $result;
    }

    //endregion

    //region purchase / Einkauf
    protected function parsePurchasing(Comments $History): void
    {
        // orders
        $purchasing = $this->getPurchasing();

        foreach ($purchasing as $Purchasing) {
            $History->addComment(
                QUI::getLocale()->get('quiqqer/erp', 'process.history.purchasing.created', [
                    'hash' => $Purchasing->getHash()
                ]),
                strtotime($Purchasing->getAttribute('c_date')),
                'quiqqer/purchasing',
                'fa fa-cart-arrow-down',
                false,
                $Purchasing->getHash()
            );

            $history = $Purchasing->getHistory()->toArray();

            foreach ($history as $entry) {
                if (empty($entry['source'])) {
                    $entry['source'] = 'quiqqer/purchasing';
                }

                if (empty($entry['sourceIcon'])) {
                    $entry['sourceIcon'] = 'fa fa-cart-arrow-down';
                }

                $History->addComment(
                    $entry['message'],
                    $entry['time'],
                    $entry['source'],
                    $entry['sourceIcon'],
                    $entry['id'],
                    $Purchasing->getHash()
                );
            }
        }
    }

    /**
     * @return array
     */
    public function getPurchasing(): array
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/purchasing')) {
            return [];
        }

        if (!class_exists('QUI\ERP\Purchasing\Processes\PurchasingProcess')) {
            return [];
        }

        if (!class_exists('QUI\ERP\Purchasing\Processes\Handler')) {
            return [];
        }

        try {
            $purchasing = QUI::getDatabase()->fetch([
                'select' => 'id,hash,global_process_id,date',
                'from' => QUI\ERP\Purchasing\Processes\Handler::getTablePurchasingProcesses(),
                'where_or' => [
                    'global_process_id' => $this->processId,
                    'hash' => $this->processId
                ]
            ]);
        } catch (\Exception) {
            return [];
        }

        $result = [];

        foreach ($purchasing as $process) {
            try {
                $result[] = QUI\ERP\Purchasing\Processes\Handler::getPurchasingProcess($process['id']);
            } catch (\Exception) {
            }
        }

        return $result;
    }

    //endregion

    //region sales orders / Aufträge
    protected function parseSalesOrders(Comments $History): void
    {
        // orders
        $salesOrders = $this->getSalesOrders();

        foreach ($salesOrders as $SalesOrder) {
            $History->addComment(
                QUI::getLocale()->get('quiqqer/erp', 'process.history.salesorders.created', [
                    'hash' => $SalesOrder->getHash()
                ]),
                strtotime($SalesOrder->getAttribute('c_date')),
                'quiqqer/salesorders',
                'fa fa-suitcase',
                false,
                $SalesOrder->getHash()
            );

            $history = $SalesOrder->getHistory()->toArray();

            foreach ($history as $entry) {
                if (empty($entry['source'])) {
                    $entry['source'] = 'quiqqer/salesorders';
                }

                if (empty($entry['sourceIcon'])) {
                    $entry['sourceIcon'] = 'fa fa-suitcase';
                }

                $History->addComment(
                    $entry['message'],
                    $entry['time'],
                    $entry['source'],
                    $entry['sourceIcon'],
                    $entry['id'],
                    $SalesOrder->getHash()
                );
            }
        }
    }

    /**
     * @return array
     */
    public function getSalesOrders(): array
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/salesorders')) {
            return [];
        }

        if (!class_exists('QUI\ERP\SalesOrders\Handler')) {
            return [];
        }

        $result = [];

        try {
            $salesOrders = QUI::getDatabase()->fetch([
                'select' => 'id,hash,global_process_id,date',
                'from' => QUI\ERP\SalesOrders\Handler::getTableSalesOrders(),
                'where_or' => [
                    'global_process_id' => $this->processId,
                    'hash' => $this->processId
                ]
            ]);
        } catch (\Exception) {
            return [];
        }

        foreach ($salesOrders as $salesOrder) {
            try {
                $result[] = QUI\ERP\SalesOrders\Handler::getSalesOrder($salesOrder['id']);
            } catch (\Exception) {
            }
        }

        // drafts
        try {
            $salesOrderDrafts = QUI::getDatabase()->fetch([
                'select' => 'id,hash,global_process_id,date',
                'from' => QUI\ERP\SalesOrders\Handler::getTableSalesOrderDrafts(),
                'where_or' => [
                    'global_process_id' => $this->processId,
                    'hash' => $this->processId
                ]
            ]);
        } catch (\Exception) {
            return [];
        }

        foreach ($salesOrderDrafts as $salesOrder) {
            try {
                $result[] = QUI\ERP\SalesOrders\Handler::getSalesOrder($salesOrder['id']);
            } catch (\Exception) {
            }
        }

        return $result;
    }

    //endregion

    //region transactions
    protected function parseTransactions(Comments $History): void
    {
        // orders
        $transactions = $this->getTransactions();

        foreach ($transactions as $Transaction) {
            $History->addComment(
                QUI::getLocale()->get('quiqqer/erp', 'process.history.transaction.created', [
                    'hash' => $Transaction->getHash(),
                    'amount' => $Transaction->getAmountFormatted()
                ]),
                strtotime($Transaction->getDate()),
                'quiqqer/payment-transaction',
                'fa fa-money',
                false,
                $Transaction->getHash()
            );
        }
    }

    /**
     * @return bool
     */
    public function hasTransactions(): bool
    {
        $transactions = $this->getTransactions();

        return !!count($transactions);
    }

    /**
     * Return all related transactions
     *
     * @return QUI\ERP\Accounting\Payments\Transactions\Transaction[];
     */
    public function getTransactions(): array
    {
        if (!QUI::getPackageManager()->isInstalled('quiqqer/payment-transactions')) {
            return [];
        }

        if ($this->transactions !== null) {
            return $this->transactions;
        }

        $Transactions = QUI\ERP\Accounting\Payments\Transactions\Handler::getInstance();

        return $Transactions->getTransactionsByProcessId($this->processId);
    }

    //endregion
}