<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */
declare (strict_types=1);
namespace Piwik\Plugins\Referrers\RecordBuilders;

use Piwik\ArchiveProcessor;
use Piwik\ArchiveProcessor\Record;
use Piwik\ArchiveProcessor\RecordBuilder;
use Piwik\Common;
use Piwik\Config;
use Piwik\DataAccess\LogAggregator;
use Piwik\DataTable;
use Piwik\Metrics;
use Piwik\Plugins\Referrers\Archiver;
use Piwik\RankingQuery;
use Piwik\Tracker\PageUrl;
class AIReferrers extends RecordBuilder
{
    private $rankingQueryLimit;
    public function __construct()
    {
        parent::__construct();
        $this->columnToSortByBeforeTruncation = Metrics::INDEX_NB_VISITS;
        // Reading pre 2.0 config file settings
        $this->maxRowsInTable = @Config::getInstance()->General['datatable_archiving_maximum_rows_referers'];
        $this->maxRowsInSubtable = @Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referers'];
        if (empty($this->maxRowsInTable)) {
            $this->maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_referrers'];
            $this->maxRowsInSubtable = Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referrers'];
        }
        $this->rankingQueryLimit = $this->getRankingQueryLimit();
    }
    public function getRecordMetadata(ArchiveProcessor $archiveProcessor) : array
    {
        return [Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME), Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME), Record::make(Record::TYPE_NUMERIC, Archiver::METRIC_DISTINCT_AI_ASSISTANT_RECORD_NAME)->setIsCountOfBlobRecordRows(Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME)];
    }
    protected function aggregate(ArchiveProcessor $archiveProcessor) : array
    {
        $records = [];
        foreach ($this->getRecordNames() as $record) {
            $records[$record] = new DataTable();
        }
        $logAggregator = $archiveProcessor->getLogAggregator();
        $this->aggregateFromVisits($logAggregator, $records[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME], 'visit_entry_idaction_url');
        $this->aggregateFromVisits($logAggregator, $records[Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME], 'visit_entry_idaction_name');
        $this->aggregateFromConversions($logAggregator, $records, ["referer_name"]);
        $records[Archiver::METRIC_DISTINCT_AI_ASSISTANT_RECORD_NAME] = count($records[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME]->getRows());
        return $records;
    }
    /**
     * @return string[]
     */
    protected function getRecordNames() : array
    {
        return [Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME, Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME];
    }
    protected function aggregateFromVisits(LogAggregator $logAggregator, DataTable $report, string $field) : void
    {
        $resultSet = $this->queryAIReferrerEntryPages($logAggregator, $field);
        while ($row = $resultSet->fetch()) {
            if (!isset($row[Metrics::INDEX_NB_VISITS])) {
                return;
            }
            $label = $row['referer_name'];
            $pageUrlOrTitle = $row['action_name'];
            if ($label === null) {
                // top-level rollup result
                continue;
            }
            if ($pageUrlOrTitle === null) {
                // second-level rollup result
                $report->sumRowWithLabel($label, $this->makeVisitRow($row));
                continue;
            }
            $tableRow = $report->getRowFromLabel($label);
            if (\false === $tableRow) {
                // non-rollup row but rollup row is missing
                // should not happen, but don't break
                continue;
            }
            if ($field === 'visit_entry_idaction_url') {
                // make sure we always work with normalized URL no matter how the individual action stores it
                $normalized = PageUrl::normalizeUrl($pageUrlOrTitle);
                $pageUrlOrTitle = $normalized['url'];
            }
            if ($pageUrlOrTitle === RankingQuery::LABEL_SUMMARY_ROW && !$tableRow->isSubtableLoaded()) {
                // skip creating the subtable if:
                // - we are using rollups
                // - the only row would be "Others"
                continue;
            }
            $tableRow->sumRowWithLabelToSubtable($pageUrlOrTitle, $this->makeVisitRow($row));
        }
    }
    /**
     * @param array<string|int, scalar> $row
     * @return array<int, float>
     */
    protected function makeVisitRow(array $row) : array
    {
        $metricIds = [Metrics::INDEX_NB_UNIQ_VISITORS, Metrics::INDEX_NB_VISITS, Metrics::INDEX_NB_ACTIONS, Metrics::INDEX_NB_USERS, Metrics::INDEX_MAX_ACTIONS, Metrics::INDEX_SUM_VISIT_LENGTH, Metrics::INDEX_BOUNCE_COUNT, Metrics::INDEX_NB_VISITS_CONVERTED];
        $columns = [];
        foreach ($metricIds as $id) {
            $columns[$id] = (float) ($row[$id] ?? 0);
        }
        return $columns;
    }
    /**
     * @param DataTable[] $reports
     */
    protected function aggregateFromConversions(LogAggregator $logAggregator, array $reports, array $dimensions) : void
    {
        $where = '%s.referer_type = ' . Common::REFERRER_TYPE_AI_ASSISTANT;
        $query = $logAggregator->queryConversionsByDimension($dimensions, $where);
        while ($row = $query->fetch()) {
            $idGoal = (int) $row['idgoal'];
            $columns = [Metrics::INDEX_GOALS => [$idGoal => Metrics::makeGoalColumnsRow($idGoal, $row)]];
            $this->aggregateConversionRow($row, $reports, $columns);
        }
        foreach ($reports as $dataTable) {
            $dataTable->filter(DataTable\Filter\EnrichRecordWithGoalMetricSums::class);
        }
    }
    /**
     * @param DataTable[] $reports
     */
    protected function aggregateConversionRow(array $row, array $reports, array $columns) : void
    {
        $reports[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME]->sumRowWithLabel($row['referer_name'], $columns);
        $reports[Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME]->sumRowWithLabel($row['referer_name'], $columns);
    }
    protected function queryAIReferrerEntryPages(LogAggregator $logAggregator, string $actionIdField)
    {
        $metricsConfig = $logAggregator->getVisitsMetricFields();
        $select = "log_visit.referer_name, COALESCE(log_action.name, '') as action_name";
        $select = $this->addMetricsToSelect($select, $metricsConfig);
        $from = ["log_visit", ["table" => "log_action", "joinOn" => "log_visit.{$actionIdField} = log_action.idaction"]];
        $where = $logAggregator->getWhereStatement('log_visit', 'visit_last_action_time');
        $where .= ' AND log_visit.referer_type = ' . Common::REFERRER_TYPE_AI_ASSISTANT;
        $groupBy = "log_visit.referer_name, action_name";
        $orderBy = "`" . Metrics::INDEX_NB_VISITS . "` DESC";
        // get query with segmentation
        $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy, 0, 0, \true);
        if ($this->rankingQueryLimit > 0) {
            $rankingQuery = new RankingQuery($this->rankingQueryLimit);
            $rankingQuery->addLabelColumn(['referer_name', 'action_name']);
            $rankingQuery->addColumn(array_keys($metricsConfig), 'sum');
            foreach ($metricsConfig as $column => $config) {
                if (empty($config['aggregation'])) {
                    continue;
                }
                $rankingQuery->addColumn($column, $config['aggregation']);
            }
            $query['sql'] = $rankingQuery->generateRankingQuery($query['sql'], \true);
        }
        $db = $logAggregator->getDb();
        return $db->query($query['sql'], $query['bind']);
    }
    private function getRankingQueryLimit() : int
    {
        $configGeneral = Config::getInstance()->General;
        $configLimit = max((int) $configGeneral['archiving_ranking_query_row_limit'], 10 * $this->maxRowsInTable);
        return $configLimit === 0 ? 0 : max($configLimit, $this->maxRowsInTable);
    }
    /**
     * @param array<int, array{aggregation: string, query: string}> $metricsConfig
     */
    private function addMetricsToSelect(string $select, array $metricsConfig) : string
    {
        if (!empty($metricsConfig)) {
            foreach ($metricsConfig as $metric => $config) {
                $select .= ', ' . $config['query'] . " as `" . $metric . "`";
            }
        }
        return $select;
    }
}
