<?php
namespace WapplerSystems\Meilisearch\Report;

/***************************************************************
 *  Copyright notice
 *
 *  (c) 2009-2015 Ingo Renner <ingo@typo3.org>
 *  All rights reserved
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
 *  free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  The GNU General Public License can be found at
 *  http://www.gnu.org/copyleft/gpl.html.
 *
 *  This script is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  This copyright notice MUST APPEAR in all copies of the script!
 ***************************************************************/

use WapplerSystems\Meilisearch\ConnectionManager;
use WapplerSystems\Meilisearch\Domain\Site\SiteRepository;
use WapplerSystems\Meilisearch\PingFailedException;
use WapplerSystems\Meilisearch\System\Meilisearch\Service\MeilisearchAdminService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Reports\Status;
use TYPO3\CMS\Reports\StatusProviderInterface;

/**
 * Provides an status report about whether a connection to the Meilisearch server can
 * be established.
 *
 * @author Ingo Renner <ingo@typo3.org>
 */
class MeilisearchStatus extends AbstractMeilisearchStatus
{

    /**
     * Site Repository
     *
     * @var SiteRepository
     */
    protected $siteRepository = null;

    /**
     * Connection Manager
     *
     * @var ConnectionManager
     */
    protected $connectionManager = null;

    /**
     * Holds the response status
     *
     * @var int
     */
    protected $responseStatus = Status::OK;

    /**
     * Holds the response message build by the checks
     *
     * @var string
     */
    protected $responseMessage = '';


    /**
     * MeilisearchStatus constructor.
     * @param SiteRepository|null $siteRepository
     * @param ConnectionManager|null $connectionManager
     */
    public function __construct(SiteRepository $siteRepository = null, ConnectionManager $connectionManager = null)
    {
        $this->siteRepository = $siteRepository ?? GeneralUtility::makeInstance(SiteRepository::class);
        $this->connectionManager = $connectionManager ?? GeneralUtility::makeInstance(ConnectionManager::class);
    }

    /**
     * Compiles a collection of status checks against each configured Meilisearch server.
     *
     */
    public function getStatus()
    {
        $reports = [];
        foreach ($this->siteRepository->getAvailableSites() as $site) {
            foreach ($site->getAllMeilisearchConnectionConfigurations() as $meilisearchConfiguration) {
                $reports[] = $this->getConnectionStatus($meilisearchConfiguration);
            }
        }

        return $reports;
    }

    /**
     * Checks whether a Meilisearch server is available and provides some information.
     *
     * @param array $meilisearchConnection Meilisearch connection parameters
     * @return Status Status of the Meilisearch connection
     */
    protected function getConnectionStatus(array $meilisearchConnection)
    {
        $header = 'Your site has contacted the Meilisearch server.';
        $this->responseStatus = Status::OK;

        $meilisearchAdmin = $this->connectionManager
            ->getMeilisearchConnectionForNodes($meilisearchConnection['read'], $meilisearchConnection['write'])
            ->getAdminService();

        $meilisearchVersion = $this->checkMeilisearchVersion($meilisearchAdmin);
        $accessFilter = $this->checkAccessFilter($meilisearchAdmin);
        $pingTime = $this->checkPingTime($meilisearchAdmin);
        $configName = $this->checkMeilisearchConfigName($meilisearchAdmin);
        $schemaName = $this->checkMeilisearchSchemaName($meilisearchAdmin);

        if ($this->responseStatus !== Status::OK) {
            $header = 'Failed contacting the Meilisearch server.';
        }

        $variables = [
            'header' => $header,
            'connection' => $meilisearchConnection,
            'meilisearch' => $meilisearchAdmin,
            'meilisearchVersion' => $meilisearchVersion,
            'pingTime' => $pingTime,
            'configName' => $configName,
            'schemaName' => $schemaName,
            'accessFilter' => $accessFilter
        ];

        $report = $this->getRenderedReport('MeilisearchStatus.html', $variables);
        return GeneralUtility::makeInstance(
            Status::class,
            /** @scrutinizer ignore-type */ 'Meilisearch',
            /** @scrutinizer ignore-type */ '',
            /** @scrutinizer ignore-type */ $report,
            /** @scrutinizer ignore-type */ $this->responseStatus
        );
    }

    /**
     * Checks the meilisearch version and adds it to the report.
     *
     * @param MeilisearchAdminService $meilisearch
     * @return string meilisearch version
     */
    protected function checkMeilisearchVersion(MeilisearchAdminService $meilisearch)
    {
        try {
            $meilisearchVersion = $this->formatMeilisearchVersion($meilisearch->getMeilisearchServerVersion());
        } catch (\Exception $e) {
            $this->responseStatus = Status::ERROR;
            $meilisearchVersion = 'Error getting meilisearch version: ' . $e->getMessage();
        }

        return $meilisearchVersion;
    }

    /**
     * Checks the access filter setup and adds it to the report.
     *
     * @param MeilisearchAdminService $meilisearchAdminService
     * @return string
     */
    protected function checkAccessFilter(MeilisearchAdminService $meilisearchAdminService)
    {
        try {
            $accessFilterPluginStatus = GeneralUtility::makeInstance(AccessFilterPluginInstalledStatus::class);
            $accessFilterPluginVersion = $accessFilterPluginStatus->getInstalledPluginVersion($meilisearchAdminService);
            $accessFilterMessage = $accessFilterPluginVersion;
        } catch (\Exception $e) {
            $this->responseStatus = Status::ERROR;
            $accessFilterMessage = 'Error getting access filter: ' . $e->getMessage();
        }
        return $accessFilterMessage;
    }

    /**
     * Checks the ping time and adds it to the report.
     *
     * @param MeilisearchAdminService $meilisearchAdminService
     * @return string
     */
    protected function checkPingTime(MeilisearchAdminService $meilisearchAdminService)
    {
        try {
            $pingQueryTime = $meilisearchAdminService->getPingRoundTripRuntime();
            $pingMessage = (int)$pingQueryTime . ' ms';
        } catch (PingFailedException $e) {
            $this->responseStatus = Status::ERROR;
            $pingMessage = 'Ping error: ' . $e->getMessage();
        }
        return $pingMessage;
    }

    /**
     * Checks the meilisearch config name and adds it to the report.
     *
     * @param MeilisearchAdminService $meilisearchAdminService
     * @return string
     */
    protected function checkMeilisearchConfigName(MeilisearchAdminService $meilisearchAdminService)
    {
        try {
            $meilisearchConfigMessage = $meilisearchAdminService->getMeilisearchconfigName();
        } catch (\Exception $e) {
            $this->responseStatus = Status::ERROR;
            $meilisearchConfigMessage = 'Error determining meilisearch config: ' . $e->getMessage();
        }

        return $meilisearchConfigMessage;
    }

    /**
     * Checks the meilisearch schema name and adds it to the report.
     *
     * @param MeilisearchAdminService $meilisearchAdminService
     * @return string
     */
    protected function checkMeilisearchSchemaName(MeilisearchAdminService $meilisearchAdminService)
    {
        try {
            $meilisearchSchemaMessage = $meilisearchAdminService->getSchema()->getName();
        } catch (\Exception $e) {
            $this->responseStatus = Status::ERROR;
            $meilisearchSchemaMessage = 'Error determining schema name: ' . $e->getMessage();
        }

        return $meilisearchSchemaMessage;
    }

    /**
     * Formats the Meilisearch server version number. By default this is going
     * to be the simple major.minor.patch-level version. Custom Builds provide
     * more information though, in case of custom builds, their complete
     * version will be added, too.
     *
     * @param string $meilisearchVersion Unformatted Meilisearch version number as provided by Meilisearch.
     * @return string formatted short version number, in case of custom builds followed by the complete version number
     */
    protected function formatMeilisearchVersion($meilisearchVersion)
    {
        $explodedMeilisearchVersion = explode('.', $meilisearchVersion);

        $shortMeilisearchVersion = $explodedMeilisearchVersion[0]
            . '.' . $explodedMeilisearchVersion[1]
            . '.' . $explodedMeilisearchVersion[2];

        $formattedMeilisearchVersion = $shortMeilisearchVersion;

        if ($meilisearchVersion != $shortMeilisearchVersion) {
            $formattedMeilisearchVersion .= ' (' . $meilisearchVersion . ')';
        }

        return $formattedMeilisearchVersion;
    }
}
