<?php

declare(strict_types=1);
/**
 * Copyright 2022-2025 FOSSBilling
 * Copyright 2011-2021 BoxBilling, Inc.
 * SPDX-License-Identifier: Apache-2.0.
 *
 * @copyright FOSSBilling (https://www.fossbilling.org)
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
 */

namespace Box\Mod\Cron;

use FOSSBilling\Config;
use FOSSBilling\Environment;
use Symfony\Component\Filesystem\Path;

class Service
{
    protected ?\Pimple\Container $di = null;

    public function setDi(\Pimple\Container $di): void
    {
        $this->di = $di;
    }

    public function getDi(): ?\Pimple\Container
    {
        return $this->di;
    }

    public function getModulePermissions(): array
    {
        return [
            'view' => [
                'type' => 'bool',
                'display_name' => __trans('View cron information'),
                'description' => __trans('Allows the staff member to view cron job information.'),
            ],
            'manage' => [
                'type' => 'bool',
                'display_name' => __trans('Run cron jobs'),
                'description' => __trans('Allows the staff member to manually execute cron jobs.'),
            ],
        ];
    }

    public function getCronInfo(): array
    {
        $service = $this->di['mod_service']('system');

        return [
            'cron_path' => Path::join(PATH_ROOT, 'cron.php'),
            'last_cron_exec' => $service->getParamValue('last_cron_exec'),
        ];
    }

    public function runCrons(): bool
    {
        if ($this->di['update_finalization']->isRequired()) {
            $this->di['logger']->setChannel('cron')->warning('Skipped cron execution because update finalization is pending.');

            throw new \FOSSBilling\InformationException('Update finalization is pending. Cron jobs are paused until finalization is completed.', [], 503);
        }

        $api = $this->di['api_system'];
        $this->di['logger']->setChannel('cron')->info('Started executing cron jobs.');

        // @core tasks
        $this->_exec($api, 'hook_batch_connect');
        $this->di['events_manager']->fire(['event' => 'onBeforeAdminCronRun']);

        $this->_exec($api, 'invoice_batch_pay_with_credits');
        $this->_exec($api, 'invoice_batch_activate_paid');
        $this->_exec($api, 'invoice_batch_send_reminders');
        $this->_exec($api, 'invoice_batch_generate');
        $this->_exec($api, 'invoice_batch_invoke_due_event');
        $this->_exec($api, 'order_batch_suspend_expired');
        $this->_exec($api, 'order_batch_cancel_suspended');
        $this->_exec($api, 'support_batch_ticket_auto_close');
        $this->_exec($api, 'support_batch_public_ticket_auto_close');
        $this->_exec($api, 'client_batch_expire_password_reminders');
        $this->_exec($api, 'cart_batch_expire');
        $this->_exec($api, 'email_batch_sendmail');

        // Update the last time cron was executed
        $this->di['mod_service']('system')->setParamValue('last_cron_exec', date('Y-m-d H:i:s'), true);

        // Purge old sessions from the DB
        $count = $this->clearOldSessions() ?? 0;
        $this->di['logger']->setChannel('cron')->info("Cleared {$count} outdated sessions from the database.");

        $this->di['events_manager']->fire(['event' => 'onAfterAdminCronRun']);
        $this->di['logger']->setChannel('cron')->info('Finished executing cron jobs.');

        return true;
    }

    /**
     * @param string $method
     */
    protected function _exec($api, $method, $params = null): void
    {
        $api->{$method}($params);

        if (Environment::isCLI()) {
            echo "\e[32mSuccessfully ran {$method}({$params}).\e[0m\n";
        }
    }

    /**
     * @return string|null
     */
    public function getLastExecutionTime()
    {
        $service = $this->di['mod_service']('system');

        return $service->getParamValue('last_cron_exec');
    }

    public function isLate(): bool
    {
        $t1 = new \DateTime($this->getLastExecutionTime());
        $t2 = new \DateTime('-6min');

        return $t1 < $t2;
    }

    private function clearOldSessions(): ?int
    {
        $maxAge = time() - Config::getProperty('security.session_lifespan', 7200);
        $sql = 'DELETE FROM session WHERE created_at <= :age';

        return $this->di['db']->exec($sql, [':age' => $maxAge]);
    }
}
