<?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\Goals\RecordBuilders;

use Piwik\ArchiveProcessor;
use Piwik\ArchiveProcessor\Record;
use Piwik\DataAccess\LogAggregator;
use Piwik\DataTable;
use Piwik\Metrics;
use Piwik\Plugin\Manager;
use Piwik\Plugins\Goals\API;
use Piwik\Plugins\Goals\Archiver;
use Piwik\Plugins\Goals\Goals;
use Piwik\Tracker\GoalManager;
class GeneralGoalsRecords extends \Piwik\Plugins\Goals\RecordBuilders\Base
{
    public const VISITS_COUNT_FIELD = 'visitor_count_visits';
    public const LOG_CONVERSION_TABLE = 'log_conversion';
    public const SECONDS_SINCE_FIRST_VISIT_FIELD = 'visitor_seconds_since_first';
    protected function aggregate(ArchiveProcessor $archiveProcessor) : array
    {
        $idSite = $this->getSiteId($archiveProcessor);
        if (empty($idSite)) {
            return [];
        }
        $prefixes = [Archiver::VISITS_UNTIL_RECORD_NAME => 'vcv', Archiver::DAYS_UNTIL_CONV_RECORD_NAME => 'vdsf'];
        $totalConversions = 0;
        $totalRevenue = 0;
        $goalMetrics = [];
        $visitsToConversions = [];
        $daysToConversions = [];
        $siteHasEcommerceOrGoals = $this->hasAnyGoalOrEcommerce($idSite);
        // Special handling for sites that contain subordinated sites, like in roll up reporting.
        // A roll up site, might not have ecommerce enabled or any configured goals,
        // but if a subordinated site has, we calculate the overview conversion metrics nevertheless
        if ($siteHasEcommerceOrGoals === \false) {
            $idSitesToArchive = $archiveProcessor->getParams()->getIdSites();
            foreach ($idSitesToArchive as $idSite) {
                if ($this->hasAnyGoalOrEcommerce($idSite)) {
                    $siteHasEcommerceOrGoals = \true;
                    break;
                }
            }
        }
        $logAggregator = $archiveProcessor->getLogAggregator();
        // try to query goal data only, if goals or ecommerce is actually used
        // otherwise we simply insert empty records
        if ($siteHasEcommerceOrGoals) {
            $selects = [];
            $selects = array_merge($selects, LogAggregator::getSelectsFromRangedColumn(self::VISITS_COUNT_FIELD, Archiver::$visitCountRanges, self::LOG_CONVERSION_TABLE, $prefixes[Archiver::VISITS_UNTIL_RECORD_NAME]));
            $selects = array_merge($selects, LogAggregator::getSelectsFromRangedColumn('FLOOR(log_conversion.' . self::SECONDS_SINCE_FIRST_VISIT_FIELD . ' / 86400)', Archiver::$daysToConvRanges, self::LOG_CONVERSION_TABLE, $prefixes[Archiver::DAYS_UNTIL_CONV_RECORD_NAME]));
            $query = $logAggregator->queryConversionsByDimension([], \false, $selects);
            if ($query === \false) {
                return [];
            }
            $conversionMetrics = $logAggregator->getConversionsMetricFields();
            while ($row = $query->fetch()) {
                $idGoal = $row['idgoal'];
                unset($row['idgoal']);
                unset($row['label']);
                foreach ($conversionMetrics as $field => $statement) {
                    $goalMetrics[$idGoal][$field] = ($goalMetrics[$idGoal][$field] ?? 0) + $row[$field];
                }
                if (empty($visitsToConversions[$idGoal])) {
                    $visitsToConversions[$idGoal] = new DataTable();
                }
                $array = LogAggregator::makeArrayOneColumn($row, Metrics::INDEX_NB_CONVERSIONS, $prefixes[Archiver::VISITS_UNTIL_RECORD_NAME]);
                $visitsToConversions[$idGoal]->addDataTable(DataTable::makeFromIndexedArray($array));
                if (empty($daysToConversions[$idGoal])) {
                    $daysToConversions[$idGoal] = new DataTable();
                }
                $array = LogAggregator::makeArrayOneColumn($row, Metrics::INDEX_NB_CONVERSIONS, $prefixes[Archiver::DAYS_UNTIL_CONV_RECORD_NAME]);
                $daysToConversions[$idGoal]->addDataTable(DataTable::makeFromIndexedArray($array));
                // We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits
                // since it is a "negative conversion"
                if ($idGoal != GoalManager::IDGOAL_CART) {
                    $totalConversions += $row[Metrics::INDEX_GOAL_NB_CONVERSIONS];
                    $totalRevenue += $row[Metrics::INDEX_GOAL_REVENUE];
                }
            }
        }
        // Stats by goal, for all visitors
        $numericRecords = $this->getConversionsNumericMetrics($goalMetrics);
        $nbConvertedVisits = $archiveProcessor->getNumberOfVisitsConverted();
        $result = array_merge([
            // Stats for all goals
            Archiver::getRecordName('nb_conversions') => $totalConversions,
            Archiver::getRecordName('nb_visits_converted') => $nbConvertedVisits,
            Archiver::getRecordName('revenue') => $totalRevenue,
        ], $numericRecords);
        foreach ($visitsToConversions as $idGoal => $table) {
            $recordName = Archiver::getRecordName(Archiver::VISITS_UNTIL_RECORD_NAME, $idGoal);
            $result[$recordName] = $table;
        }
        $result[Archiver::getRecordName(Archiver::VISITS_UNTIL_RECORD_NAME)] = $this->getOverviewFromGoalTables($visitsToConversions);
        foreach ($daysToConversions as $idGoal => $table) {
            $recordName = Archiver::getRecordName(Archiver::DAYS_UNTIL_CONV_RECORD_NAME, $idGoal);
            $result[$recordName] = $table;
        }
        $result[Archiver::getRecordName(Archiver::DAYS_UNTIL_CONV_RECORD_NAME)] = $this->getOverviewFromGoalTables($daysToConversions);
        return $result;
    }
    private function getOverviewFromGoalTables(array $tableByGoal) : DataTable
    {
        $overview = new DataTable();
        foreach ($tableByGoal as $idGoal => $table) {
            if ($this->isStandardGoal($idGoal)) {
                $overview->addDataTable($table);
            }
        }
        return $overview;
    }
    private function isStandardGoal(int $idGoal) : bool
    {
        return !in_array($idGoal, $this->getEcommerceIdGoals());
    }
    public function getRecordMetadata(ArchiveProcessor $archiveProcessor) : array
    {
        $goals = API::getInstance()->getGoals($this->getSiteId($archiveProcessor));
        $goals = array_keys($goals);
        if (Manager::getInstance()->isPluginActivated('Ecommerce')) {
            $goals = array_merge($goals, $this->getEcommerceIdGoals());
        }
        // Overall goal metrics
        $goals[] = \false;
        $records = [];
        foreach ($goals as $idGoal) {
            $metricsToSum = Goals::getGoalColumns($idGoal);
            foreach ($metricsToSum as $metricName) {
                $records[] = Record::make(Record::TYPE_NUMERIC, Archiver::getRecordName($metricName, $idGoal));
            }
            $records[] = Record::make(Record::TYPE_BLOB, Archiver::getRecordName(Archiver::VISITS_UNTIL_RECORD_NAME, $idGoal));
            $records[] = Record::make(Record::TYPE_BLOB, Archiver::getRecordName(Archiver::DAYS_UNTIL_CONV_RECORD_NAME, $idGoal));
        }
        return $records;
    }
    protected function getConversionsNumericMetrics(array $goals) : array
    {
        $numericRecords = [];
        foreach ($goals as $idGoal => $array) {
            foreach ($array as $metricId => $value) {
                $metricName = Metrics::$mappingFromIdToNameGoal[$metricId];
                $recordName = Archiver::getRecordName($metricName, $idGoal);
                $numericRecords[$recordName] = $value;
            }
        }
        return $numericRecords;
    }
    public function isEnabled(ArchiveProcessor $archiveProcessor) : bool
    {
        return $archiveProcessor->getNumberOfVisitsConverted() > 0 || $this->usesEcommerce($archiveProcessor->getParams()->getSite()->getId());
    }
}
