<?php
/**
 * @package     Ezone.Package.Artigen
 *
 * @copyright   Copyright (C) 2025 Multizone Limited. All rights reserved.
 * @license     GNU General Public License version 3 or later; see LICENSE
 * @author      Angus Fox, Multizone Limited <admin@multizone.co.uk>
 *
 * @since       2.6.0
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerScript;

/**
 * Installation script for Article Generator Package
 */
class Pkg_ArtiGenInstallerScript extends InstallerScript
{
    /**
     * Minimum Joomla version required to install the extension.
     *
     * @var    string
     */
    protected $minimumJoomla = '4.4.0';

    /**
     * Minimum PHP version required to install the extension.
     *
     * @var    string
     */
    protected $minimumPhp = '8.1.0';

    /**
     * Method to install the package.
     *
     * @param   InstallerAdapter  $parent  The class calling this method
     *
     * @return  boolean  True on success
     */
    public function install($parent)
    {
        $app = Factory::getApplication();

        $app->enqueueMessage(
            '<h2>AI Article Generator for Joomla</h2>' .
            '<strong>Installed Successfully!</strong><br><br>' .
            'This package includes:<br>' .
            '&bull; Article Generator Component (com_artigen)<br>' .
            '&bull; Artigen Tasks Plugin (plg_task_artigentasks)<br><br>' .
            'Configure the component at Components &rarr; Article Generator',
            'success'
        );

        return true;
    }

    /**
     * Method to uninstall the package.
     *
     * @param   InstallerAdapter  $parent  The class calling this method
     *
     * @return  boolean  True on success
     */
    public function uninstall($parent)
    {
        Factory::getApplication()->enqueueMessage(
            'Article Generator Package has been uninstalled. All extensions have been removed.',
            'info'
        );

        return true;
    }

    /**
     * Method to update the package.
     *
     * @param   InstallerAdapter  $parent  The class calling this method
     *
     * @return  boolean  True on success
     */
    public function update($parent)
    {
        return true;
    }

    /**
     * Method to run before an install/update/uninstall method.
     *
     * @param   string            $type    The type of change (install, update or discover_install)
     * @param   InstallerAdapter  $parent  The class calling this method
     *
     * @return  boolean  True on success
     */
    public function preflight($type, $parent)
    {
        if (!parent::preflight($type, $parent)) {
            return false;
        }

        return true;
    }

    /**
     * Method to run after an install/update/uninstall method.
     *
     * @param   string            $type    The type of change (install, update or discover_install)
     * @param   InstallerAdapter  $parent  The class calling this method
     *
     * @return  boolean  True on success
     */
    public function postflight($type, $parent)
    {
        if ($type !== 'uninstall') {
            $this->setupUpdateServer();
            $this->cleanupOrphanedUpdateSites();
            $this->removeLegacySystemTasksPlugin();
            $this->enableTaskPlugin();
            $this->syncSubExtensionVersions($parent);
        }

        return true;
    }

    /**
     * Enable plg_task_artigentasks after install / upgrade.
     *
     * Joomla's installer leaves a freshly-installed plugin disabled
     * by default. The artigentasks plugin is the carrier for the
     * Scheduler routines; if it's disabled the Scheduler doesn't
     * load it and the "Article Generator — News Beat Cadence" /
     * "Process Batch Queue" routine options never appear in
     * System → Scheduled Tasks → New. Enable explicitly so admin
     * doesn't have to chase that.
     *
     * @return  void
     * @since   3.4.67
     */
    private function enableTaskPlugin(): void
    {
        try {
            $db    = Factory::getDbo();
            $query = $db->getQuery(true)
                ->update($db->quoteName('#__extensions'))
                ->set($db->quoteName('enabled') . ' = 1')
                ->where($db->quoteName('element') . ' = ' . $db->quote('artigentasks'))
                ->where($db->quoteName('folder') . ' = ' . $db->quote('task'))
                ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'));
            $db->setQuery($query);
            $db->execute();
        } catch (\Throwable $e) {
            // Non-critical — admin can enable manually.
        }
    }

    /**
     * Remove the legacy plg_system_artigentasks row from #__extensions.
     *
     * The plugin moved from group=system → group=task in 3.4.66 because
     * Joomla's Scheduler only loads task-group plugins. Joomla's
     * installer treats the new file as a separate plugin (different
     * group), so the old system row stays orphaned in the table.
     * Drop it explicitly so admins don't see two Artigen Tasks
     * entries in the Plugin Manager.
     *
     * @return  void
     * @since   3.4.66
     */
    private function removeLegacySystemTasksPlugin(): void
    {
        try {
            $db    = Factory::getDbo();
            $query = $db->getQuery(true)
                ->delete($db->quoteName('#__extensions'))
                ->where($db->quoteName('element') . ' = ' . $db->quote('artigentasks'))
                ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
                ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'));
            $db->setQuery($query);
            $db->execute();
        } catch (\Throwable $e) {
            // Non-critical — admin can clean up manually if needed.
        }
    }

    /**
     * Synchronize sub-extension versions with package version
     *
     * When Joomla updates a package, it runs SQL updates for sub-extensions but
     * doesn't always update the manifest XML files or manifest_cache version.
     * This causes the "problem found" warning in database maintenance where
     * schema version > manifest version.
     *
     * @param   InstallerAdapter  $parent  The class calling this method
     *
     * @return  void
     */
    private function syncSubExtensionVersions($parent)
    {
        $db = Factory::getDbo();

        try {
            $manifest = $parent->getManifest();
            $packageVersion = (string) $manifest->version;

            if (empty($packageVersion)) {
                return;
            }

            $subExtensions = [
                [
                    'element' => 'com_artigen',
                    'type' => 'component',
                    'folder' => '',
                    'manifest_path' => JPATH_ADMINISTRATOR . '/components/com_artigen/com_artigen.xml'
                ],
                [
                    'element' => 'artigentasks',
                    'type' => 'plugin',
                    'folder' => 'task',
                    'manifest_path' => JPATH_PLUGINS . '/task/artigentasks/artigentasks.xml'
                ],
            ];

            foreach ($subExtensions as $ext) {
                $query = $db->getQuery(true)
                    ->select(['extension_id', 'manifest_cache'])
                    ->from($db->quoteName('#__extensions'))
                    ->where('element = ' . $db->quote($ext['element']))
                    ->where('type = ' . $db->quote($ext['type']));

                if (!empty($ext['folder'])) {
                    $query->where('folder = ' . $db->quote($ext['folder']));
                }

                $db->setQuery($query);
                $extension = $db->loadObject();

                if ($extension) {
                    $manifestCache = json_decode($extension->manifest_cache, true) ?: [];
                    $manifestCache['version'] = $packageVersion;

                    $updateQuery = $db->getQuery(true)
                        ->update($db->quoteName('#__extensions'))
                        ->set('manifest_cache = ' . $db->quote(json_encode($manifestCache)))
                        ->where('extension_id = ' . (int) $extension->extension_id);

                    $db->setQuery($updateQuery);
                    $db->execute();

                    // Run any pending SQL migrations for component sub-extensions
                    if ($ext['type'] === 'component') {
                        $this->runPendingMigrations($extension->extension_id, $ext['element'], $packageVersion);
                    }
                }

                if (!empty($ext['manifest_path']) && file_exists($ext['manifest_path']) && is_writable($ext['manifest_path'])) {
                    $this->updateManifestVersion($ext['manifest_path'], $packageVersion);
                }
            }

        } catch (\Exception $e) {
            \Joomla\CMS\Log\Log::add(
                'Failed to sync sub-extension versions: ' . $e->getMessage(),
                \Joomla\CMS\Log\Log::WARNING,
                'com_artigen'
            );
        }
    }

    /**
     * Run pending SQL migrations for a component sub-extension
     *
     * @param   int     $extensionId   Extension ID from #__extensions
     * @param   string  $element       Extension element (e.g. com_artigen)
     * @param   string  $targetVersion Target version string
     *
     * @return  void
     */
    private function runPendingMigrations($extensionId, $element, $targetVersion)
    {
        $db = Factory::getDbo();

        try {
            $query = $db->getQuery(true)
                ->select('version_id')
                ->from($db->quoteName('#__schemas'))
                ->where('extension_id = ' . (int) $extensionId);
            $db->setQuery($query);
            $currentVersion = $db->loadResult();

            $sqlDir = JPATH_ADMINISTRATOR . '/components/' . $element . '/sql/updates/mysql';

            if (!is_dir($sqlDir)) {
                if ($currentVersion === null) {
                    $db->setQuery(
                        'INSERT INTO ' . $db->quoteName('#__schemas') . ' (extension_id, version_id) VALUES ('
                        . (int) $extensionId . ', ' . $db->quote($targetVersion) . ')'
                    )->execute();
                }
                return;
            }

            $files = glob($sqlDir . '/*.sql');
            $pending = [];

            foreach ($files as $file) {
                $fileVersion = basename($file, '.sql');
                if ($currentVersion === null || version_compare($fileVersion, $currentVersion, '>')) {
                    if (version_compare($fileVersion, $targetVersion, '<=')) {
                        $pending[$fileVersion] = $file;
                    }
                }
            }

            uksort($pending, 'version_compare');

            foreach ($pending as $version => $file) {
                $sql = file_get_contents($file);
                if (empty($sql)) {
                    continue;
                }

                $queries = $db->splitSql($sql);
                foreach ($queries as $q) {
                    $q = trim($q);
                    if (empty($q)) {
                        continue;
                    }
                    try {
                        $db->setQuery($q)->execute();
                    } catch (\Exception $e) {
                        \Joomla\CMS\Log\Log::add(
                            'Migration ' . $version . ' warning: ' . $e->getMessage(),
                            \Joomla\CMS\Log\Log::WARNING,
                            $element
                        );
                    }
                }
            }

            // Set schema to highest SQL file version (Joomla expects this, not extension version)
            $schemaVersion = $targetVersion;
            foreach ($files as $file) {
                $fv = basename($file, '.sql');
                if (version_compare($fv, $schemaVersion, '>')) {
                    $schemaVersion = $fv;
                }
            }

            if ($currentVersion === null) {
                $db->setQuery(
                    'INSERT INTO ' . $db->quoteName('#__schemas') . ' (extension_id, version_id) VALUES ('
                    . (int) $extensionId . ', ' . $db->quote($schemaVersion) . ')'
                )->execute();
            } else {
                $db->setQuery(
                    $db->getQuery(true)
                        ->update($db->quoteName('#__schemas'))
                        ->set('version_id = ' . $db->quote($schemaVersion))
                        ->where('extension_id = ' . (int) $extensionId)
                )->execute();
            }

        } catch (\Exception $e) {
            \Joomla\CMS\Log\Log::add(
                'Failed to run pending migrations for ' . $element . ': ' . $e->getMessage(),
                \Joomla\CMS\Log\Log::WARNING,
                $element
            );
        }
    }

    /**
     * Update version in a manifest XML file
     *
     * @param   string  $manifestPath  Path to manifest XML file
     * @param   string  $version       New version string
     *
     * @return  bool  True on success
     */
    private function updateManifestVersion($manifestPath, $version)
    {
        try {
            $content = file_get_contents($manifestPath);
            if ($content === false) {
                return false;
            }

            $updated = preg_replace(
                '/<version>[^<]*<\/version>/',
                '<version>' . $version . '</version>',
                $content
            );

            if ($updated !== $content) {
                return file_put_contents($manifestPath, $updated) !== false;
            }

            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Remove orphaned update sites for the standalone com_artigen extension
     *
     * When migrating from standalone com_artigen to pkg_artigen, legacy update
     * sites for the component should be removed.
     *
     * @return void
     */
    private function cleanupOrphanedUpdateSites()
    {
        $db = Factory::getDbo();

        try {
            $findQuery = $db->getQuery(true)
                ->select('us.update_site_id')
                ->from($db->quoteName('#__update_sites', 'us'))
                ->where('(' .
                    'us.location LIKE ' . $db->quote('%com_artigen.xml%') .
                    ' OR us.location LIKE ' . $db->quote('%extension=com_artigen%') .
                    ' OR us.name LIKE ' . $db->quote('%Article Generator - EZONE%') .
                ')');
            $db->setQuery($findQuery);
            $orphanedSites = $db->loadColumn();

            if (!empty($orphanedSites)) {
                $deleteMappingQuery = $db->getQuery(true)
                    ->delete($db->quoteName('#__update_sites_extensions'))
                    ->where('update_site_id IN (' . implode(',', array_map('intval', $orphanedSites)) . ')');
                $db->setQuery($deleteMappingQuery);
                $db->execute();

                $deleteSiteQuery = $db->getQuery(true)
                    ->delete($db->quoteName('#__update_sites'))
                    ->where('update_site_id IN (' . implode(',', array_map('intval', $orphanedSites)) . ')');
                $db->setQuery($deleteSiteQuery);
                $db->execute();
            }
        } catch (\Exception $e) {
            // Silent fail - not critical
        }
    }

    /**
     * Setup update server with download key support
     *
     * @return void
     */
    private function setupUpdateServer()
    {
        $db = Factory::getDbo();

        try {
            $extensionQuery = $db->getQuery(true)
                ->select('extension_id')
                ->from($db->quoteName('#__extensions'))
                ->where('element = ' . $db->quote('pkg_artigen'))
                ->where('type = ' . $db->quote('package'));
            $db->setQuery($extensionQuery);
            $extensionId = $db->loadResult();

            if (!$extensionId) {
                return;
            }

            $siteQuery = $db->getQuery(true)
                ->select('update_site_id')
                ->from($db->quoteName('#__update_sites'))
                ->where('location LIKE ' . $db->quote('%pkg_artigen.xml'));
            $db->setQuery($siteQuery);
            $existingSiteId = $db->loadResult();

            if (!$existingSiteId) {
                $insertSiteQuery = $db->getQuery(true)
                    ->insert($db->quoteName('#__update_sites'))
                    ->columns(['name', 'type', 'location', 'enabled', 'last_check_timestamp', 'extra_query'])
                    ->values(implode(',', [
                        $db->quote('Article Generator Package'),
                        $db->quote('extension'),
                        $db->quote('https://www.multizone.co.uk/updates/pkg_artigen.xml'),
                        1,
                        0,
                        $db->quote('')
                    ]));
                $db->setQuery($insertSiteQuery);
                $db->execute();
                $newSiteId = $db->insertid();

                $insertMappingQuery = $db->getQuery(true)
                    ->insert($db->quoteName('#__update_sites_extensions'))
                    ->columns(['update_site_id', 'extension_id'])
                    ->values($newSiteId . ',' . $extensionId);
                $db->setQuery($insertMappingQuery);
                $db->execute();
            }

        } catch (\Exception $e) {
            // Silent fail - not critical
        }
    }
}
