<?php
namespace DevOwl\RealCookieBanner\scanner;

use DevOwl\HeadlessContentBlocker\Markup;
use DevOwl\HeadlessContentBlocker\plugins\scanner\ScanEntry;
use DevOwl\RealCookieBanner\base\UtilsProvider;

// @codeCoverageIgnoreStart
defined('ABSPATH') or die('No script kiddies please!'); // Avoid direct file request
// @codeCoverageIgnoreEnd

/**
 * Persist multiple `ScanEntry`'s from the `Scanner` results to the database.
 *
 * It also provides functionality to avoid duplicate found templates (e.g. MonsterInsights over Google Analytics),
 * and deduplicate coexisting templates (e.g. CF7 with reCaptcha over Google reCaptcha).
 */
class Persist {
    use UtilsProvider;

    const TABLE_NAME = 'scan';

    const TABLE_NAME_MARKUP = 'scan_markup';

    /**
     * Fields which should be updated via `ON DUPLICATE KEY UPDATE`.
     */
    const DECLARATION_OVERWRITE_FIELDS = ['post_id', 'markup_hash'];

    private $entries;

    /**
     * C'tor.
     *
     * @codeCoverageIgnore
     * @param ScanEntry[] $entries
     */
    public function __construct($entries) {
        $this->entries = $entries;
    }

    /**
     * Insert entries to database.
     */
    public function persist() {
        global $wpdb;

        if (count($this->entries) === 0) {
            return;
        }

        $table_name = $this->getTableName(self::TABLE_NAME);
        $table_name_markup = $this->getTableName(self::TABLE_NAME_MARKUP);
        $post_id = get_the_ID();

        /**
         * Persist distinct markups.
         *
         * @var Markup[]
         */
        $distinctMarkups = array_values(array_unique(array_column($this->entries, 'markup')));
        $rows = [];
        foreach ($distinctMarkups as $distinctMarkup) {
            // Generate `VALUES` SQL
            // phpcs:disable WordPress.DB.PreparedSQL
            $rows[] = str_ireplace(
                ["'NULL'", '= NULL'],
                ['NULL', 'IS NULL'],
                $wpdb->prepare('%s, %s', $distinctMarkup->getId(), $distinctMarkup->getContent())
            );
            // phpcs:enable WordPress.DB.PreparedSQL
        }

        // Chunk to boost performance
        $chunks = array_chunk($rows, 150);
        foreach ($chunks as $sqlInsert) {
            $sql =
                "INSERT IGNORE INTO $table_name_markup (`markup_hash`, `markup`) VALUES (" .
                implode('),(', $sqlInsert) .
                ')';
            // phpcs:disable WordPress.DB.PreparedSQL
            $wpdb->query($sql);
            // phpcs:enable WordPress.DB.PreparedSQL
        }

        /**
         * Persist scan results.
         */
        $rows = [];
        foreach ($this->entries as $entry) {
            // Generate `VALUES` SQL
            // phpcs:disable WordPress.DB.PreparedSQL
            $rows[] = str_ireplace(
                ["'NULL'", '= NULL'],
                ['NULL', 'IS NULL'],
                $wpdb->prepare(
                    '%s, %s, %s, %s, %s, %s, %d, %s, %s, %s',
                    $entry->template,
                    $entry->blocked_url ?? 'NULL',
                    $entry->blocked_url_host ?? 'NULL',
                    $entry->blocked_url_hash,
                    $entry->markup === null ? '' : $entry->markup->getId(),
                    $entry->tag,
                    $post_id,
                    $entry->source_url,
                    $entry->source_url_hash,
                    current_time('mysql')
                )
            );
            // phpcs:enable WordPress.DB.PreparedSQL
        }

        // Allow to update fields if already exists
        $overwriteSql = [];
        foreach (self::DECLARATION_OVERWRITE_FIELDS as $field) {
            $overwriteSql[] = sprintf('%1$s=VALUES(%1$s)', $field);
        }

        // Chunk to boost performance
        $chunks = array_chunk($rows, 150);
        foreach ($chunks as $sqlInsert) {
            $sql =
                "INSERT INTO $table_name
                    (`preset`, `blocked_url`, `blocked_url_host`, `blocked_url_hash`, `markup_hash`, `tag`, `post_id`, `source_url`, `source_url_hash`, `created`)
                    VALUES (" .
                implode('),(', $sqlInsert) .
                ')
                    ON DUPLICATE KEY UPDATE ' .
                join(', ', $overwriteSql);
            // phpcs:disable WordPress.DB.PreparedSQL
            $wpdb->query($sql);
            // phpcs:enable WordPress.DB.PreparedSQL
        }
    }

    /**
     * Get the persistable entries.
     *
     * @codeCoverageIgnore
     */
    public function getEntries() {
        return $this->entries;
    }
}
