<?php

namespace YSquare\GiftCert\Cron;

use OxidEsales\Eshop\Application\Model\Article;
use OxidEsales\Eshop\Application\Model\Order;
use OxidEsales\Eshop\Core\Email;
use OxidEsales\Eshop\Core\Registry;
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface;
use Throwable;
use YSquare\GiftCert\Core\DebugLogger;
use YSquare\GiftCert\Core\GiftCertHandler;
use YSquare\GiftCert\Model\GiftCert;

class GiftCertJobs
{

    /**
     * @var mixed
     */
    protected $queryBuilderFactory;

    /**
     * GiftCertHandler constructor.
     */
    public function __construct()
    {
        $container = ContainerFactory::getInstance()->getContainer();
        $this->queryBuilderFactory = $container->get(QueryBuilderFactoryInterface::class);
    }

    /**
     *
     */
    public function sendGiftcerts()
    {
        $logger = Registry::getLogger();

        // Load Settings
        $daysBeforeDeletion = Registry::getConfig()->getConfigParam('iYsGiftcertKeepBrokenOrdersInDays');
        $daysBeforeDeletion = is_numeric($daysBeforeDeletion) ? intval($daysBeforeDeletion) : 3;
        $acceptedOrderStates = array_map('strtolower',
            Registry::getConfig()->getConfigParam('aYsGiftcertPaymentSuccessfulStates') ?: []
        );

        $queryBuilder = $this->queryBuilderFactory->create();
        $queryBuilder
            ->select('*')
            ->from("ysgiftcert")
            ->where('ysdelivered = 0')
            ->andWhere('ysdeliverydate <= :deliverydate')
            ->setParameter("deliverydate", date("Y-m-d"));

        $blocksData = $queryBuilder->execute();
        $blocksData = $blocksData->fetchAll();

        DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'enter', [
            'pendingCount'        => count($blocksData),
            'acceptedOrderStates' => $acceptedOrderStates,
            'daysBeforeDeletion'  => $daysBeforeDeletion,
        ]);

        $stats = ['sent' => 0, 'skippedNoRecipient' => 0, 'skippedBusiness' => 0,
            'skippedOrphan' => 0, 'skippedOrderGone' => 0, 'skippedNotPaid' => 0,
            'deletedNotPaid' => 0, 'pdfFailed' => 0, 'recipientMailFailed' => 0];

        foreach ($blocksData as $entry) {
            // Reset all iteration-local state explicitly so a leak from the previous
            // iteration (e.g. orphan cert without oxorderid) cannot reach the mail send.
            $shopid = $langid = null;
            $purchaserFirstName = $purchaserLastName = '';
            $orderStatus = null;
            $orderDate = null;

            $giftcert = oxNew(GiftCert::class);
            if (!$giftcert->load($entry["OXID"])) {
                DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'could not load cert row', [
                    'oxid' => $entry["OXID"],
                ]);
                continue;
            }
            $certNr = $giftcert->ysgiftcert__ysgiftcertnr->value;
            $options = json_decode($entry["YSINDIVIDUALOPTIONS"], true);

            DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'processing cert', [
                'certNr'         => $certNr,
                'oxorderid'      => $giftcert->ysgiftcert__oxorderid->value,
                'deliveryOption' => $giftcert->ysgiftcert__ysdeliveryoption->value,
                'deliveryDate'   => $giftcert->ysgiftcert__ysdeliverydate->value,
            ]);

            // No recipient mail → nothing to send, mark as delivered to stop re-processing
            if (trim($entry["YSINDIVIDUALOPTIONS"]) == "" || !isset($options["recipientMail"]) || trim($options["recipientMail"]) == "") {
                DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'no recipient mail — marking delivered', ['certNr' => $certNr]);
                $giftcert->markAsDelivered();
                $stats['skippedNoRecipient']++;
                continue;
            }

            // Business gift cert: never delivered by email through cron
            if (isset($options["isBusinessGiftcert"]) && $options["isBusinessGiftcert"] == 1) {
                DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'business cert — marking delivered', ['certNr' => $certNr]);
                $giftcert->markAsDelivered();
                $stats['skippedBusiness']++;
                continue;
            }

            // Orphan cert (oxorderid never assigned) — we cannot determine the order context,
            // so we skip it (without marking delivered) and let the operator investigate.
            if (trim($giftcert->ysgiftcert__oxorderid->value) === "") {
                DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'cert has empty oxorderid — skipping', [
                    'certNr' => $certNr,
                    'certOxid' => $giftcert->getId(),
                ]);
                $stats['skippedOrphan']++;
                continue;
            }

            // Load related order
            $order = oxNew(Order::class);
            if (!$order->load($giftcert->ysgiftcert__oxorderid->value)) {
                // Order was deleted — assume the purchase was aborted, do not retry forever
                DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'order gone — marking cert delivered to stop retries', [
                    'certNr'    => $certNr,
                    'oxorderid' => $giftcert->ysgiftcert__oxorderid->value,
                ]);
                $giftcert->markAsDelivered();
                $stats['skippedOrderGone']++;
                continue;
            }
            $shopid             = $order->oxorder__oxshopid->value;
            $langid             = $order->oxorder__oxlang->value;
            $purchaserFirstName = $order->oxorder__oxbillfname->value;
            $purchaserLastName  = $order->oxorder__oxbilllname->value;
            $orderStatus        = $order->oxorder__oxtransstatus->value;
            $orderDate          = new \DateTime($order->oxorder__oxorderdate->value);

            // Payment check
            $paid = in_array(strtolower($orderStatus), $acceptedOrderStates);
            if (!$paid) {
                $currentDate = new \DateTime();
                $interval = $currentDate->diff($orderDate);

                $logger->error('This giftcert is invalid due to not paid order: ' . $certNr . " // ". $order->oxorder__oxordernr->value . " // " . $interval->days ." of ".$daysBeforeDeletion, [__CLASS__, __FUNCTION__]);

                if ($interval->invert && $interval->days >= $daysBeforeDeletion) {
                    DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'order still not paid after threshold — deleting cert', [
                        'certNr'       => $certNr,
                        'orderNr'      => $order->oxorder__oxordernr->value,
                        'orderStatus'  => $orderStatus,
                        'ageInDays'    => $interval->days,
                        'threshold'    => $daysBeforeDeletion,
                    ]);
                    $logger->error('- Order still not paid after threshold. Deleting the giftcert. ' , [__CLASS__, __FUNCTION__]);
                    $giftcert->delete();
                    $stats['deletedNotPaid']++;
                } else {
                    DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'order not paid — waiting', [
                        'certNr'      => $certNr,
                        'orderStatus' => $orderStatus,
                        'ageInDays'   => $interval->days,
                    ]);
                    $stats['skippedNotPaid']++;
                }
                continue;
            }

            // Produce the PDF. If this fails we do NOT mark the cert as delivered — the next
            // cron run gets another chance once the underlying issue (disk space, fonts, …)
            // is resolved.
            $giftcertHandler = oxNew(GiftCertHandler::class);
            $pdfFile = $giftcertHandler->generateAttachmentPDF($giftcert);
            if ($pdfFile === null) {
                DebugLogger::error('GiftCertJobs::sendGiftcerts', 'PDF generation failed — leaving cert undelivered for retry', [
                    'certNr' => $certNr,
                ]);
                $stats['pdfFailed']++;
                continue;
            }
            $filesToAttach = [$pdfFile];

            // The recipient mail is the load-bearing one: if it fails (SMTP down, bad address,
            // template error, …) we keep the cert pending so we can retry on the next run.
            // The purchaser/owner mails are confirmation copies — a failure there is logged
            // but does not block delivery.
            $recipientOk = false;
            try {
                $email = oxNew(Email::class);
                $recipientOk = (bool) $email->sendGiftcertEmailToRecipient(
                    $giftcert, $filesToAttach, $langid, $shopid, $purchaserFirstName, $purchaserLastName
                );
            } catch (Throwable $e) {
                DebugLogger::error('GiftCertJobs::sendGiftcerts', 'recipient mail threw exception', [
                    'certNr'    => $certNr,
                    'exception' => get_class($e),
                    'message'   => $e->getMessage(),
                ]);
                $recipientOk = false;
            }
            DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'recipient mail result', [
                'certNr' => $certNr,
                'ok'     => $recipientOk,
            ]);

            if (!$recipientOk) {
                DebugLogger::error('GiftCertJobs::sendGiftcerts', 'recipient mail failed — NOT marking delivered, will retry next run', [
                    'certNr' => $certNr,
                ]);
                @unlink($pdfFile);
                $stats['recipientMailFailed']++;
                continue;
            }

            // Recipient got the cert — from here on we consider the delivery completed.
            $giftcert->markAsDelivered();
            $stats['sent']++;

            try {
                $email = oxNew(Email::class);
                $okP = (bool) $email->sendGiftcertEmailToPurchaser($giftcert, $filesToAttach, $langid, $shopid);
                DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'purchaser mail result', ['certNr' => $certNr, 'ok' => $okP]);
                if (!$okP) {
                    DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'purchaser copy failed (delivery still considered complete)', ['certNr' => $certNr]);
                }
            } catch (Throwable $e) {
                DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'purchaser mail threw exception (delivery still considered complete)', [
                    'certNr' => $certNr, 'exception' => get_class($e), 'message' => $e->getMessage(),
                ]);
            }

            try {
                $email = oxNew(Email::class);
                $okO = (bool) $email->sendGiftcertEmailToShopOwner($giftcert, $filesToAttach, $langid, $shopid);
                DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'shop owner mail result', ['certNr' => $certNr, 'ok' => $okO]);
                if (!$okO) {
                    DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'shop owner copy failed (delivery still considered complete)', ['certNr' => $certNr]);
                }
            } catch (Throwable $e) {
                DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'shop owner mail threw exception (delivery still considered complete)', [
                    'certNr' => $certNr, 'exception' => get_class($e), 'message' => $e->getMessage(),
                ]);
            }

            // Cleanup PDF
            foreach ($filesToAttach as $file) {
                $unlinked = @unlink($file);
                if (!$unlinked) {
                    DebugLogger::warning('GiftCertJobs::sendGiftcerts', 'pdf cleanup unlink failed', [
                        'certNr'   => $certNr,
                        'filename' => $file,
                        'exists'   => file_exists($file),
                    ]);
                }
            }
        }

        DebugLogger::debug('GiftCertJobs::sendGiftcerts', 'done', $stats);
    }
}
