<?php

/**
 * The Admin Update Controller
 *
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at https://mozilla.org/MPL/2.0/.
 *
 * @package   phpMyFAQ
 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
 * @copyright 2023-2026 phpMyFAQ Team
 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
 * @link      https://www.phpmyfaq.de
 * @since     2023-07-29
 */

declare(strict_types=1);

namespace phpMyFAQ\Controller\Administration\Api;

use DateTime;
use DateTimeInterface;
use phpMyFAQ\Controller\AbstractController;
use phpMyFAQ\Core\Exception;
use phpMyFAQ\Enums\PermissionType;
use phpMyFAQ\Filter;
use phpMyFAQ\Session\Token;
use phpMyFAQ\System;
use phpMyFAQ\Translation;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;

final class UpdateController extends AbstractController
{
    /**
     * @throws Exception|\Exception
     */
    #[Route(path: 'admin/api/health-check')]
    public function healthCheck(): JsonResponse
    {
        $this->userIsAuthenticated();

        $dateTime = new DateTime();
        $dateLastChecked = $dateTime->format(DateTimeInterface::ATOM);
        $upgrade = $this->container->get(id: 'phpmyfaq.setup.upgrade');

        if (!$upgrade->isMaintenanceEnabled()) {
            return $this->json([
                'warning' => Translation::get(key: 'msgNotInMaintenanceMode'),
                'dateLastChecked' => $dateLastChecked,
            ], Response::HTTP_CONFLICT);
        }

        try {
            $upgrade->checkFilesystem();
            return $this->json([
                'success' => Translation::get(key: 'healthCheckOkay'),
                'dateLastChecked' => $dateLastChecked,
            ], Response::HTTP_OK);
        } catch (Exception $exception) {
            return $this->json([
                'error' => $exception->getMessage(),
                'dateLastChecked' => $dateLastChecked,
            ], Response::HTTP_BAD_REQUEST);
        }
    }

    #[Route(path: 'admin/api/versions')]
    public function versions(): JsonResponse
    {
        $this->userIsAuthenticated();

        try {
            $versions = HttpClient::create(['timeout' => 30])->request('GET', 'https://api.phpmyfaq.de/versions');
            return $this->json($versions->getContent(), Response::HTTP_OK);
        } catch (
            TransportExceptionInterface|ClientExceptionInterface|ServerExceptionInterface|RedirectionExceptionInterface $exception
        ) {
            return $this->json($exception->getMessage(), Response::HTTP_BAD_REQUEST);
        }
    }

    /**
     * @throws Exception|\Exception
     */
    #[Route(path: 'admin/api/update-check')]
    public function updateCheck(): JsonResponse
    {
        $this->userIsAuthenticated();

        $dateTime = new DateTime();
        $dateLastChecked = $dateTime->format(DateTimeInterface::ATOM);
        $branch = $this->configuration->get(item: 'upgrade.releaseEnvironment');

        try {
            $versions = $this->container->get(id: 'phpmyfaq.admin.api')->getVersions();
            $this->configuration->set('upgrade.dateLastChecked', $dateLastChecked);

            $installed = $versions['installed'];
            $available = $versions[$branch];

            if (version_compare($installed, $available, operator: '<')) {
                return $this->json([
                    'version' => $available,
                    'message' => Translation::get(key: 'msgCurrentVersion') . $available,
                    'dateLastChecked' => $dateLastChecked,
                ], Response::HTTP_OK);
            }

            if ($branch !== 'nightly' && version_compare($installed, $available, operator: '>')) {
                return $this->json([
                    'version' => $available,
                    'message' => Translation::get(key: 'msgInstalledNewerThanAvailable'),
                    'dateLastChecked' => $dateLastChecked,
                ], Response::HTTP_CONFLICT);
            }

            return $this->json([
                'version' => $installed,
                'message' => Translation::get(key: 'versionIsUpToDate'),
                'dateLastChecked' => $dateLastChecked,
            ], Response::HTTP_OK);
        } catch (TransportExceptionInterface|DecodingExceptionInterface $e) {
            return $this->json(['error' => $e->getMessage()], Response::HTTP_BAD_REQUEST);
        }
    }

    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     * @throws \JsonException
     * @throws Exception|\Exception
     */
    #[Route(path: 'admin/api/download-package')]
    public function downloadPackage(Request $request): JsonResponse
    {
        $this->userHasPermission(PermissionType::CONFIGURATION_EDIT);

        if (!$this->isValidUpdatePackageToken($request)) {
            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
        }

        $versionNumber = Filter::filterVar($request->attributes->get('versionNumber'), FILTER_SANITIZE_SPECIAL_CHARS);

        $upgrade = $this->container->get(id: 'phpmyfaq.setup.upgrade');
        try {
            $pathToPackage = $upgrade->downloadPackage($versionNumber);
        } catch (Exception $exception) {
            return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
        }

        if ($pathToPackage === false) {
            return $this->json(['error' => Translation::get(key: 'downloadFailure')], Response::HTTP_BAD_GATEWAY);
        }

        if (!$upgrade->isNightly()) {
            $result = $upgrade->verifyPackage($pathToPackage, $versionNumber);
            if ($result === false) {
                return $this->json([
                    'error' => Translation::get(key: 'verificationFailure'),
                ], Response::HTTP_BAD_GATEWAY);
            }
        }

        $this->configuration->set('upgrade.lastDownloadedPackage', urlencode($pathToPackage));

        return $this->json(['success' => Translation::get(key: 'downloadSuccessful')], Response::HTTP_OK);
    }

    #[Route(path: 'admin/api/extract-package')]
    public function extractPackage(Request $request): Response
    {
        $this->userHasPermission(PermissionType::CONFIGURATION_EDIT);

        if (!$this->isValidUpdatePackageToken($request)) {
            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
        }

        $upgrade = $this->container->get(id: 'phpmyfaq.setup.upgrade');
        $pathToPackage = urldecode((string) $this->configuration->get(item: 'upgrade.lastDownloadedPackage'));

        return new StreamedResponse(static function () use ($upgrade, $pathToPackage): void {
            $progressCallback = static function ($progress): void {
                echo json_encode(['progress' => $progress]) . "\n";
                ob_flush();
                flush();
            };
            if ($upgrade->extractPackage($pathToPackage, $progressCallback)) {
                echo json_encode(['message' => Translation::get(key: 'extractSuccessful')]);
            } else {
                echo json_encode(['message' => Translation::get(key: 'extractFailure')]);
            }
        });
    }

    #[Route(path: 'admin/api/create-temporary-backup')]
    public function createTemporaryBackup(Request $request): Response
    {
        $this->userHasPermission(PermissionType::CONFIGURATION_EDIT);

        if (!$this->isValidUpdatePackageToken($request)) {
            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
        }

        $upgrade = $this->container->get(id: 'phpmyfaq.setup.upgrade');
        $backupHash = md5(uniqid());

        return new StreamedResponse(static function () use ($upgrade, $backupHash): void {
            $progressCallback = static function ($progress): void {
                echo json_encode(['progress' => $progress]) . "\n";
                ob_flush();
                flush();
            };
            if ($upgrade->createTemporaryBackup($backupHash . '.zip', $progressCallback)) {
                echo json_encode(['message' => 'Backup successful']);
            } else {
                echo json_encode(['message' => 'Backup failed']);
            }
        });
    }

    #[Route(path: 'admin/api/install-package')]
    public function installPackage(Request $request): Response
    {
        $this->userHasPermission(PermissionType::CONFIGURATION_EDIT);

        if (!$this->isValidUpdatePackageToken($request)) {
            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
        }

        $upgrade = $this->container->get(id: 'phpmyfaq.setup.upgrade');
        $configurator = $this->container->get(id: 'phpmyfaq.setup.environment_configurator');
        return new StreamedResponse(static function () use ($upgrade, $configurator): void {
            $progressCallback = static function ($progress): void {
                echo json_encode(['progress' => $progress]) . "\n";
                ob_flush();
                flush();
            };
            if ($upgrade->installPackage($progressCallback) && $configurator->adjustRewriteBaseHtaccess()) {
                echo json_encode(['message' => 'Package successfully installed.']);
            } else {
                echo json_encode(['message' => 'Install package failed']);
            }
        });
    }

    #[Route(path: 'admin/api/update-database')]
    public function updateDatabase(): JsonResponse
    {
        $this->userHasPermission(PermissionType::CONFIGURATION_EDIT);

        $update = $this->container->get(id: 'phpmyfaq.setup.update');
        $update->setVersion($this->configuration->get('main.currentVersion'));

        try {
            if ($update->applyUpdates()) {
                $this->configuration->set('main.maintenanceMode', 'false');
                return new JsonResponse(['success' => 'Database successfully updated.'], Response::HTTP_OK);
            }

            $this->configuration->set('main.maintenanceMode', 'false');
            return new JsonResponse(['error' => 'Update database failed.'], Response::HTTP_BAD_GATEWAY);
        } catch (Exception|\Exception $exception) {
            $this->configuration->set('main.maintenanceMode', 'false');
            return new JsonResponse([
                'error' => 'Update database failed: ' . $exception->getMessage(),
            ], Response::HTTP_BAD_GATEWAY);
        }
    }

    /**
     * @throws Exception|\Exception
     */
    #[Route(path: 'admin/api/cleanup')]
    public function cleanUp(): JsonResponse
    {
        $this->userHasPermission(PermissionType::CONFIGURATION_EDIT);

        $upgrade = $this->container->get(id: 'phpmyfaq.setup.upgrade');
        $upgrade->cleanUp();

        return $this->json(['message' => 'Cleanup successful.'], Response::HTTP_OK);
    }

    /**
     * Verifies the CSRF token sent with the updater package actions.
     */
    private function isValidUpdatePackageToken(Request $request): bool
    {
        $data = json_decode((string) $request->getContent());
        $csrfToken = is_object($data) ? (string) ($data->csrf ?? '') : '';

        return Token::getInstance($this->container->get(id: 'session'))->verifyToken('update-package', $csrfToken);
    }
}
