<?php
/**
 * Upgrades to version 6.0.0-b2
 *
 * @package blesta
 * @subpackage components.upgrades.tasks
 * @copyright Copyright (c) 2026, Phillips Data, Inc.
 * @license http://www.blesta.com/license/ The Blesta License Agreement
 * @link http://www.blesta.com/ Blesta
 */
class Upgrade6_0_0B2 extends UpgradeUtil
{
    /**
     * @var array An array of all tasks completed
     */
    private $tasks = [];

    /**
     * Setup
     */
    public function __construct()
    {
        Loader::loadComponents($this, ['Record']);
    }

    /**
     * Returns a numerically indexed array of tasks to execute for the upgrade process
     *
     * @return array A numerically indexed array of tasks to execute for the upgrade process
     */
    public function tasks()
    {
        return [
            'addNotificationsCompanyId',
            'addTransactionIndexes',
            'removeLegacyModuleFieldFiles',
        ];
    }

    /**
     * Processes the given task
     *
     * @param string $task The task to process
     */
    public function process($task)
    {
        $tasks = $this->tasks();

        // Ensure task exists
        if (!in_array($task, $tasks)) {
            return;
        }

        $this->tasks[] = $task;
        $this->{$task}();
    }

    /**
     * Rolls back all tasks completed for the upgrade process
     */
    public function rollback()
    {
        // Undo all tasks
        while (($task = array_pop($this->tasks))) {
            $this->{$task}(true);
        }
    }

    /**
     * Adds the company_id column to the notifications table so the admin notification
     * bell can be made company-aware. The owning company is backfilled from each
     * notification's action (notification_actions.company_id). The existing
     * (user_id, read) index is replaced with (user_id, date_added) so the dropdown's
     * hot query -- getAll(['user_id' => X]) ORDER BY date_added DESC -- is served
     * without a filesort; the small per-user unread count still uses the user_id
     * prefix. company_id is intentionally not indexed since the dropdown keeps
     * cross-company rows and never filters on it.
     *
     * @param bool $undo Whether to undo the upgrade
     */
    private function addNotificationsCompanyId($undo = false)
    {
        if ($undo) {
            $this->Record->query('ALTER TABLE `notifications` DROP INDEX `user_id_date_added`;');
            $this->Record->query('ALTER TABLE `notifications` ADD INDEX `user_id` (`user_id`, `read`);');
            $this->Record->query('ALTER TABLE `notifications` DROP COLUMN `company_id`;');
        } else {
            // Add the owning-company column (nullable so existing rows survive)
            $this->Record->query(
                'ALTER TABLE `notifications` ADD COLUMN `company_id` INT(10) UNSIGNED NULL AFTER `user_id`;'
            );
            // Backfill the owning company from each notification's action
            $this->Record->query(
                'UPDATE `notifications` `n`'
                . ' INNER JOIN `notification_actions` `a` ON `a`.`id` = `n`.`action_id`'
                . ' SET `n`.`company_id` = `a`.`company_id`;'
            );
            // Replace the (user_id, read) index with one that serves the dropdown's
            // ordered-by-date_added list query without a filesort
            $this->Record->query('ALTER TABLE `notifications` DROP INDEX `user_id`;');
            $this->Record->query(
                'ALTER TABLE `notifications` ADD INDEX `user_id_date_added`'
                . ' (`user_id`, `date_added`);'
            );
        }
    }

    /**
     * Adds composite indexes to the transactions table to speed up the transaction list and
     * status-count queries, which filter by company (via client) and status while ordering by
     * date_added. Without these the database cannot satisfy the ORDER BY date_added + LIMIT from
     * an index and must build and sort the full result set.
     *
     * @param bool $undo Whether to undo the upgrade
     */
    private function addTransactionIndexes($undo = false)
    {
        if ($undo) {
            $this->Record->query('ALTER TABLE `transactions` DROP INDEX `status_date_added`;');
            $this->Record->query('ALTER TABLE `transactions` DROP INDEX `client_status_date_added`;');
        } else {
            // Serves the company-wide list/count: filter on status, order by date_added
            $this->Record->query(
                'ALTER TABLE `transactions` ADD INDEX `status_date_added` (`status`, `date_added`);'
            );
            // Serves the per-client list/count: filter on client_id and status, order by date_added
            $this->Record->query(
                'ALTER TABLE `transactions` ADD INDEX `client_status_date_added`'
                . ' (`client_id`, `status`, `date_added`);'
            );
        }
    }

    /**
     * Removes the deprecated module field files left over from versions prior to 6.0.0-b1
     *
     * As of 6.0.0-b1 the ModuleField/ModuleFields classes were removed and are now provided
     * as runtime aliases of InputField/InputFields (see config/aliases.php). Upgrades do not
     * normally unlink obsolete files, so on upgraded systems these legacy files remain on disk
     * and re-declare those class names, colliding with the alias and causing a fatal error
     * (HTTP 500). Removing them resolves the conflict. Fresh installs will not have these files,
     * so the deletion is guarded by an existence check.
     *
     * @param bool $undo True to undo the change false to perform the change
     */
    private function removeLegacyModuleFieldFiles($undo = false)
    {
        if ($undo) {
            // Don't recreate deprecated files
            return;
        }

        foreach (['module_field.php', 'module_fields.php'] as $file) {
            $path = COMPONENTDIR . 'modules' . DS . $file;
            if (file_exists($path)) {
                @unlink($path);
            }
        }
    }
}
