<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */
namespace Piwik\Plugins\Insights;

use Piwik\API\Request as ApiRequest;
use Piwik\DataTable;
use Piwik\Piwik;
/**
 * Provides API methods for insight and mover/shaker comparisons between report periods.
 *
 * @method static \Piwik\Plugins\Insights\API getInstance()
 */
class API extends \Piwik\Plugin\API
{
    /**
     * Include only 'movers' which are existing in the current and past report.
     */
    public const FILTER_BY_MOVERS = 'movers';
    /**
     * Include only 'new' rows which were not existing in the past report.
     */
    public const FILTER_BY_NEW = 'new';
    /**
     * Include only 'disappeared' rows which were existing in the past report but no longer in the current report.
     */
    public const FILTER_BY_DISAPPEARED = 'disappeared';
    /**
     * @var Model
     */
    private $model;
    public function __construct(\Piwik\Plugins\Insights\Model $model)
    {
        $this->model = $model;
    }
    /**
     * @return array<string, array<string, scalar>>
     */
    private function getOverviewReports() : array
    {
        $reports = [];
        /**
         * Triggered to gather all reports to be displayed in the "Insight" and "Movers And Shakers" overview reports.
         * Plugins that want to add new reports to the overview should subscribe to this event and add reports to the
         * incoming array. API parameters can be configured as an array optionally.
         *
         * **Example**
         *
         *     public function addReportToInsightsOverview(&$reports)
         *     {
         *         $reports['Actions_getPageUrls']  = array();
         *         $reports['Actions_getDownloads'] = array('flat' => 1, 'minGrowthPercent' => 60);
         *     }
         *
         * @param array &$reports An array containing a report unique id as key and an array of API parameters as
         *                        values.
         */
        Piwik::postEvent('Insights.addReportToOverview', array(&$reports));
        return $reports;
    }
    /**
     * Detects whether insights can be generated for this date/period combination or not.
     *
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @return bool Whether a previous comparison period exists for the requested date/period combination.
     */
    public function canGenerateInsights(string $date, string $period) : bool
    {
        Piwik::checkUserHasSomeViewAccess();
        try {
            $lastDate = $this->model->getLastDate($date, $period, 1);
        } catch (\Exception $e) {
            return \false;
        }
        if (empty($lastDate)) {
            return \false;
        }
        return \true;
    }
    /**
     * Generates insights for a set of reports. Plugins can add their own reports to be included in the insights
     * overview by listening to the {@hook Insights.addReportToOverview} event.
     *
     * @param int $idSite The numeric ID of the website to query.
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @return DataTable\Map Insight tables for every report included in the overview.
     */
    public function getInsightsOverview(int $idSite, string $period, string $date, $segment = \false)
    {
        Piwik::checkUserHasViewAccess($idSite);
        $defaultParams = array('limitIncreaser' => 3, 'limitDecreaser' => 3, 'minImpactPercent' => 1, 'minGrowthPercent' => 25);
        return $this->generateOverviewReport('getInsights', $idSite, $period, $date, $segment, $defaultParams);
    }
    /**
     * Detects the movers and shakers for a set of reports. Plugins can add their own reports to be included in this
     * overview by listening to the {@hook Insights.addReportToOverview} event.
     *
     * @param int $idSite The numeric ID of the website to query.
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @return DataTable\Map Movers-and-shakers tables for every report included in the overview.
     */
    public function getMoversAndShakersOverview(int $idSite, string $period, string $date, $segment = \false)
    {
        Piwik::checkUserHasViewAccess($idSite);
        $defaultParams = array('limitIncreaser' => 4, 'limitDecreaser' => 4);
        return $this->generateOverviewReport('getMoversAndShakers', $idSite, $period, $date, $segment, $defaultParams);
    }
    /**
     * @param string|null|false $segment
     * @param array<string, scalar> $defaultParams
     */
    private function generateOverviewReport(string $method, int $idSite, string $period, string $date, $segment, array $defaultParams) : DataTable\Map
    {
        $tableManager = DataTable\Manager::getInstance();
        $map = new DataTable\Map();
        foreach ($this->getOverviewReports() as $reportId => $reportParams) {
            if (!empty($reportParams)) {
                foreach ($defaultParams as $key => $defaultParam) {
                    if (!array_key_exists($key, $reportParams)) {
                        $reportParams[$key] = $defaultParam;
                    }
                }
            }
            $firstTableId = $tableManager->getMostRecentTableId();
            /** @var DataTable $table */
            $table = $this->requestApiMethod($method, $idSite, $period, $date, $reportId, $segment, $reportParams);
            $reportTableIds[] = $table->getId();
            $tableManager->deleteTablesExceptIgnored($reportTableIds, $firstTableId);
            $map->addTable($table, $table->getMetadata('reportName'));
        }
        return $map;
    }
    /**
     * Detects the movers and shakers of a given date / report combination. A mover and shakers has an higher impact
     * than other rows on average. For instance if a sites pageviews increase by 10% a page that increased by 40% at the
     * same time contributed significantly more to the success than the average of 10%.
     *
     * @param int $idSite The numeric ID of the website to query.
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param string $reportUniqueId Report identifier, for example `Actions_getPageUrls`.
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @param int $comparedToXPeriods Number of past periods to compare against.
     * @param int $limitIncreaser Maximum number of positive movers to include. `0` excludes them.
     * @param int $limitDecreaser Maximum number of negative movers to include. `0` excludes them.
     * @return DataTable Movers-and-shakers rows for the requested report.
     */
    public function getMoversAndShakers(int $idSite, string $period, string $date, string $reportUniqueId, $segment = \false, $comparedToXPeriods = 1, $limitIncreaser = 4, $limitDecreaser = 4)
    {
        Piwik::checkUserHasViewAccess([$idSite]);
        $metric = 'nb_visits';
        $orderBy = \Piwik\Plugins\Insights\InsightReport::ORDER_BY_ABSOLUTE;
        $reportMetadata = $this->model->getReportByUniqueId($idSite, $reportUniqueId);
        if (empty($reportMetadata)) {
            throw new \Exception('A report having the ID ' . $reportUniqueId . ' does not exist');
        }
        $totalValue = $this->model->getTotalValue($idSite, $period, $date, $metric, $segment);
        $currentReport = $this->model->requestReport($idSite, $period, $date, $reportUniqueId, $metric, $segment);
        $this->checkReportIsValid($currentReport);
        $lastDate = $this->model->getLastDate($date, $period, $comparedToXPeriods);
        $lastTotalValue = $this->model->getTotalValue($idSite, $period, $lastDate, $metric, $segment);
        $lastReport = $this->model->requestReport($idSite, $period, $lastDate, $reportUniqueId, $metric, $segment);
        $this->checkReportIsValid($lastReport);
        $insight = new \Piwik\Plugins\Insights\InsightReport();
        return $insight->generateMoverAndShaker($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $lastTotalValue, $orderBy, $limitIncreaser, $limitDecreaser);
    }
    /**
     * Generates insights by comparing the report for a given date/period with a different date and calculating the
     * difference. The API can exclude rows which growth is not good enough or did not have enough impact.
     *
     * @param int $idSite The numeric ID of the website to query.
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param string $reportUniqueId Report identifier, for example `Actions_getPageUrls`.
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @param int $limitIncreaser Maximum number of positive movers to include. `0` excludes them.
     * @param int $limitDecreaser Maximum number of negative movers to include. `0` excludes them.
     * @param ''|'movers'|'new'|'disappeared' $filterBy Optional filter for mover type.
     * @param int $minImpactPercent Minimum impact threshold in percent.
     * @param int $minGrowthPercent Minimum growth threshold in percent compared to the previous period.
     * @param int $comparedToXPeriods Number of past periods to compare against.
     * @param 'absolute'|'relative'|'importance' $orderBy Row ordering mode.
     * @return DataTable Insight rows for the requested report.
     */
    public function getInsights(int $idSite, string $period, string $date, string $reportUniqueId, $segment = \false, $limitIncreaser = 5, $limitDecreaser = 5, string $filterBy = '', $minImpactPercent = 2, $minGrowthPercent = 20, $comparedToXPeriods = 1, string $orderBy = 'absolute')
    {
        Piwik::checkUserHasViewAccess(array($idSite));
        $metric = 'nb_visits';
        $reportMetadata = $this->model->getReportByUniqueId($idSite, $reportUniqueId);
        if (empty($reportMetadata)) {
            throw new \Exception('A report having the ID ' . $reportUniqueId . ' does not exist');
        }
        $totalValue = $this->model->getTotalValue($idSite, $period, $date, $metric, $segment);
        $currentReport = $this->model->requestReport($idSite, $period, $date, $reportUniqueId, $metric, $segment);
        $this->checkReportIsValid($currentReport);
        $lastDate = $this->model->getLastDate($date, $period, $comparedToXPeriods);
        $lastTotalValue = $this->model->getTotalValue($idSite, $period, $lastDate, $metric, $segment);
        $lastReport = $this->model->requestReport($idSite, $period, $lastDate, $reportUniqueId, $metric, $segment);
        $this->checkReportIsValid($lastReport);
        $minGrowthPercentPositive = abs($minGrowthPercent);
        $minGrowthPercentNegative = -1 * $minGrowthPercentPositive;
        $relevantTotal = $this->model->getRelevantTotalValue($currentReport, $metric, $totalValue);
        $minMoversPercent = -1;
        $minNewPercent = -1;
        $minDisappearedPercent = -1;
        switch ($filterBy) {
            case self::FILTER_BY_MOVERS:
                $minMoversPercent = $minImpactPercent;
                break;
            case self::FILTER_BY_NEW:
                $minNewPercent = $minImpactPercent;
                break;
            case self::FILTER_BY_DISAPPEARED:
                $minDisappearedPercent = $minImpactPercent;
                break;
            default:
                $minMoversPercent = $minImpactPercent;
                $minNewPercent = $minImpactPercent;
                $minDisappearedPercent = $minImpactPercent;
        }
        $insight = new \Piwik\Plugins\Insights\InsightReport();
        $table = $insight->generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $relevantTotal, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy, $limitIncreaser, $limitDecreaser);
        $insight->markMoversAndShakers($table, $currentReport, $lastReport, $totalValue, $lastTotalValue);
        return $table;
    }
    /**
     * @param mixed $report
     */
    private function checkReportIsValid($report) : void
    {
        if (!$report instanceof DataTable) {
            throw new \Exception('Insight can be only generated for reports returning a dataTable');
        }
    }
    /**
     * @param string|null|false $segment
     * @param array<string, scalar> $additionalParams
     * @return DataTable|DataTable\Map
     */
    private function requestApiMethod(string $method, int $idSite, string $period, string $date, string $reportId, $segment, $additionalParams)
    {
        $params = array('idSite' => $idSite, 'date' => $date, 'period' => $period, 'format' => 'original', 'reportUniqueId' => $reportId, 'totals' => 0);
        if (!empty($segment)) {
            $params['segment'] = $segment;
        }
        if (!empty($additionalParams)) {
            foreach ($additionalParams as $key => $value) {
                $params[$key] = $value;
            }
        }
        return ApiRequest::processRequest('Insights.' . $method, $params, $default = []);
    }
}
