<?php

use Joomla\CMS\Installer\InstallerHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\Component\Installer\Administrator\Model\UpdatesitesModel;

class WatchfulliExtensionUpdater
{
    /** @var WatchfulliAppSwitcher */
    private $appSwitcher;

    public function __construct()
    {
        $this->appSwitcher = new WatchfulliAppSwitcher();
    }

    /**
     * @throws Exception
     */
    public function update(stdClass $extParams): array
    {
        Watchfulli::debug("WatchfulliExtensionUpdater::doInstall - starting");

        $context = [
            'extParams' => $extParams,
        ];

        if (empty($extParams->update_url)) {
            return [
                'task' => 'install',
                'status' => 'error',
                'message' => 'COM_JMONITORING_CANT_GET_UPDATE_URL',
            ];
        }

        $file = $this->downloadFile($extParams);

        if (!$file) {
            $message = $extParams->is_paid && !$extParams->has_valid_license_key ? 'COM_JMONITORING_PRO_NOT_AVAILABLE' : 'COM_JMONITORING_CANT_DOWNLOAD_UPDATE';
            return [
                'task' => 'install',
                'status' => 'error',
                'message' => $message,
                'context' => $context,
            ];
        }

        try {
            $package = $this->unpackFile($file, $extParams->package_name);
        } catch (Exception $ex) {
            $context['exception'] = $ex->getMessage();
            $context['exceptionTrace'] = $ex->getTraceAsString();

            return [
                'task' => 'install',
                'status' => 'exception',
                'message' => 'COM_JMONITORING_CANT_UNPACK_UPDATE',
                'context' => $context,
            ];
        }
        Watchfulli::debug("WatchfulliExtensionUpdater::doInstall - file unpacked", ['package' => $package]);

        $installer = Installer::getInstance();

        $this->appSwitcher->switchToWatchfulApp();

        Watchfulli::debug("WatchfulliExtensionUpdater::doInstall - switched to Watchful app");

        $installerResult = $installer->install($package['dir']);

        Watchfulli::debug("WatchfulliExtensionUpdater::doInstall - got result", ['installerResult' => $installerResult]);

        if ($installerResult === false && !WatchfulliFactory::getApplication()->installStatus) {
           $context['installStatus'] = WatchfulliFactory::getApplication()->installStatus;

            return [
                'task' => 'install',
                'status' => 'error',
                'message' => 'COM_JMONITORING_CANT_INSTALL_UPDATE',
                'context' => $context,
            ];
        }

        Watchfulli::debug("WatchfulliExtensionUpdater::doInstall - package installed");

        $this->appSwitcher->switchToOriginalApp();

        InstallerHelper::cleanupInstall($package['packagefile'], $package['extractdir']);

        $message = "ok_".$installer->manifest->version;

        return [
            'task' => 'install',
            'status' => 'success',
            'message' => $message,
        ];
    }

    /**
     * Unpack a given file
     *
     * @param string $file the name of the file to unpack
     *
     * @return array|bool
     * @throws Exception
     */
    private function unpackFile(string $file, $packageName)
    {
        if (!$file) {
            throw new Exception('COM_JMONITORING_CANT_UNPACK_UPDATE_EMPTY_FILE');
        }

        $tmp_path = WatchfulliFactory::getConfig()->get('tmp_path', JPATH_SITE.'/tmp');
        if (!file_exists($tmp_path)) {
            throw new Exception('COM_JMONITORING_CANT_UNPACK_UPDATE_WRONG_TMP_PATH');
        }

        if ($packageName && ($file != $packageName)) {
            WatchfulliFileHelper::move($tmp_path.'/'.$file, $tmp_path.'/'.$packageName);
            $file = $packageName;
        }

        if (!file_exists($tmp_path.'/'.$file)) {
            throw new Exception('COM_JMONITORING_CANT_UNPACK_UPDATE_MISSING_FILE');
        }

        $package = InstallerHelper::unpack($tmp_path.'/'.$file);
        if (empty($package)) {
            throw new Exception('COM_JMONITORING_CANT_UNPACK_UPDATE');
        }

        return $package;
    }

    private function downloadFile($extParams)
    {
        $localDownloadKeyUrl = $this->getLocalDownloadKeyUrl($extParams);
        Watchfulli::debug("WatchfulliExtensionUpdater::downloadFile - localDownloadKeyUrl: $localDownloadKeyUrl");

        $file = InstallerHelper::downloadPackage($localDownloadKeyUrl ?: $extParams->update_url, $extParams->package_name);
        Watchfulli::debug("WatchfulliExtensionUpdater::doInstall - package downloaded");

        if (!$file && $localDownloadKeyUrl) {
            Watchfulli::debug("WatchfulliExtensionUpdater::doInstall - package download failed");
            $file = InstallerHelper::downloadPackage($extParams->update_url, $extParams->package_name);
        }

        return $file;
    }

    private function getLocalDownloadKeyUrl($extParams)
    {
        if (version_compare(WatchfulliJoomlaVersion::getVersion(), '4.0', '<')) {
            return false;
        }

        try {
            $key = $this->getDownloadKey($extParams);
        } catch (Exception $e) {
            return false;
        }

        if (!$key) {
            return false;
        }

        if (
            empty($key['supported']) ||
            empty($key['valid']) ||
            empty($key['prefix']) ||
            empty($key['value'])
        ) {
            return false;
        }

        if (substr($key['prefix'], -1) !== '=') {
            return false;
        }

        $param = substr($key['prefix'], 0, -1);

        $url = parse_url($extParams->update_url);
        $url['query'] = $url['query'] ?? '';

        parse_str($url['query'], $query);

        $value = $key['value'] . ($key['suffix'] ?? '');

        $query[$param] = $value;

        $url['query'] = http_build_query($query);

        return $url['scheme'] . '://' . $url['host'] . $url['path'] . '?' . $url['query'];
    }

    private function getDownloadKey($extParams)
    {
        $updates = (new UpdatesitesModel())->getItems();

        $updatesWithKey = array_filter($updates, function ($update) use ($extParams) {
            return $update->element === $extParams->ext_prefix && !empty($update->downloadKey) && $update->downloadKey['supported'] && $update->downloadKey['valid'];
        });

        if (empty($updatesWithKey)) {
            return false;
        }

        $update = reset($updatesWithKey);

        return $update->downloadKey;
    }
}