<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magento\Framework\DB\Adapter;

use Magento\Framework\App\ResourceConnection;

/**
 * Class GetDbVersion provides sql engine version requesting version variable
 *
 * Rather then depending on this class, please implement this logic in your extension
 */
class SqlVersionProvider
{
    /**#@+
     * Database version specific templates
     */
    public const MYSQL_8_0_VERSION = '8.0.';

    public const MYSQL_5_7_VERSION = '5.7.';

    public const MARIA_DB_10_VERSION = '10.';

    public const MARIA_DB_10_4_VERSION = '10.4.';

    public const MARIA_DB_10_6_VERSION = '10.6.';

    public const MYSQL_8_0_29_VERSION = '8.0.29';

    public const MARIA_DB_10_6_11_VERSION = '10.6.11';

    public const MARIA_DB_10_4_27_VERSION = '10.4.27';

    public const MARIA_DB_10_11_VERSION = '10.11.';

    public const MARIA_DB_10_11_11_VERSION = '10.11.11';

    public const MARIA_DB_11_4_VERSION = '11.4.';

    public const MARIA_DB_11_8_VERSION = '11.8.';

    public const MARIA_DB = 'mariadb';

    /**
     * Database version variable name
     */
    private const VERSION_VAR_NAME = 'version';

    /**
     * @var ResourceConnection
     */
    private $resourceConnection;

    /**
     * @var string
     */
    private $version;

    /**
     * @var array
     */
    private $supportedVersionPatterns;

    /**
     * @param ResourceConnection $resourceConnection
     * @param array $supportedVersionPatterns
     */
    public function __construct(
        ResourceConnection $resourceConnection,
        array $supportedVersionPatterns = []
    ) {
        $this->resourceConnection = $resourceConnection;
        $this->supportedVersionPatterns = $supportedVersionPatterns;
    }

    /**
     * Provides SQL engine version (MariaDB, MySQL-8, MySQL-5.7)
     *
     * @param string $resource
     *
     * @return string
     * @throws ConnectionException
     */
    public function getSqlVersion(string $resource = ResourceConnection::DEFAULT_CONNECTION): string
    {
        if (!$this->version) {
            $this->version = $this->getVersionString($resource);
        }

        return $this->version;
    }

    /**
     * Provides Sql Engine Version string
     *
     * @param string $resource
     *
     * @return string
     * @throws ConnectionException
     */
    private function getVersionString(string $resource): string
    {
        $pattern = sprintf('/(%s)/', implode('|', $this->supportedVersionPatterns));
        $sqlVersionOutput = $this->fetchSqlVersion($resource);
        preg_match($pattern, $sqlVersionOutput, $match);
        if (empty($match)) {
            throw new ConnectionException(
                sprintf(
                    "Current version of RDBMS is not supported. Used Version: %s. Supported versions: %s",
                    $sqlVersionOutput,
                    implode(', ', array_keys($this->supportedVersionPatterns))
                )
            );
        }

        return reset($match);
    }

    /**
     * Fetch version from sql engine
     *
     * @param string $resource
     *
     * @return string
     */
    private function fetchSqlVersion(string $resource): string
    {
        $versionOutput = $this->resourceConnection->getConnection($resource)
            ->fetchPairs(sprintf('SHOW variables LIKE "%s"', self::VERSION_VAR_NAME));

        return $versionOutput[self::VERSION_VAR_NAME];
    }

    /**
     * Check if MySQL version is greater than equal to 8.0.29
     *
     * @return bool
     * @throws ConnectionException
     */
    public function isMysqlGte8029(): bool
    {
        $isMariaDB = $this->isMariaDbEngine();
        $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION);
        if (!$isMariaDB && version_compare($sqlExactVersion, '8.0.29', '>=')) {
            return true;
        }
        return false;
    }

    /**
     * Checks if MariaDB is used as SQL engine
     *
     * @return bool
     * @throws ConnectionException
     */
    public function isMariaDbEngine(): bool
    {
        $this->getSqlVersion();
        $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION);
        return str_contains(strtolower($sqlExactVersion), self::MARIA_DB);
    }

    /**
     * Get MariaDB suffix key for fixture selection (10.4.27, 10.6.11, 10.11.11; 11.4/11.8 use 10.6.11 format).
     *
     * @return string
     * @throws ConnectionException
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    public function getMariaDbSuffixKey(): string
    {
        $sqlVersion = $this->getSqlVersion();
        $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION);
        $isMariaDB104 = str_contains($sqlVersion, self::MARIA_DB_10_4_VERSION);
        $isMariaDB106 = str_contains($sqlVersion, self::MARIA_DB_10_6_VERSION);
        $isMariaDB1011 = str_contains($sqlVersion, self::MARIA_DB_10_11_VERSION);
        $isMariaDB114 = str_contains($sqlVersion, '11.4');
        $isMariaDB118 = str_contains($sqlVersion, '11.8');
        if ($isMariaDB114 && version_compare($sqlExactVersion, '11.4.0', '>=')) {
            return self::MARIA_DB_10_6_11_VERSION;
        }
        if ($isMariaDB118 && version_compare($sqlExactVersion, '11.8.0', '>=')) {
            return self::MARIA_DB_10_6_11_VERSION;
        }
        if (version_compare($sqlExactVersion, '10.4.27', '>=')) {
            if ($isMariaDB104) {
                return self::MARIA_DB_10_4_27_VERSION;
            }
            if ($isMariaDB106) {
                return self::MARIA_DB_10_6_11_VERSION;
            }
            if ($isMariaDB1011 && version_compare($sqlExactVersion, '10.11.10', '>=')) {
                return self::MARIA_DB_10_11_11_VERSION;
            }
        }
        return self::MARIA_DB_10_6_11_VERSION;
    }

    /**
     * Check if MariaDB version is greater than equal to 10.6.11
     *
     * @return bool
     * @throws ConnectionException
     */
    public function isMariaDBGte10611(): bool
    {
        $sqlVersion = $this->getSqlVersion();
        $isMariaDB106 = str_contains($sqlVersion, SqlVersionProvider::MARIA_DB_10_6_VERSION);
        $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION);
        if ($isMariaDB106 && version_compare($sqlExactVersion, '10.6.11', '>=')) {
            return true;
        }
        return false;
    }

    /**
     * Check if MariaDB version is greater than equal to 10.11.11
     *
     * @return bool
     * @throws ConnectionException
     */
    public function isMariaDBGte101111(): bool
    {
        $sqlVersion = $this->getSqlVersion();
        $isMariaDB1011 = str_contains($sqlVersion, SqlVersionProvider::MARIA_DB_10_11_VERSION);
        $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION);
        if ($isMariaDB1011 && version_compare($sqlExactVersion, '10.11.10', '>=')) {
            return true;
        }
        return false;
    }

    /**
     * Check if MariaDB version is greater than equal to 10.4.27
     *
     * @return bool
     * @throws ConnectionException
     */
    public function isMariaDBGte10427(): bool
    {
        $sqlVersion = $this->getSqlVersion();
        $isMariaDB104 = str_contains($sqlVersion, SqlVersionProvider::MARIA_DB_10_4_VERSION);
        $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION);
        if ($isMariaDB104 && version_compare($sqlExactVersion, '10.4.27', '>=')) {
            return true;
        }
        return false;
    }

    /**
     * Check if MariaDB version is 11.4.x (compatibility mapped to 10.6.11 behavior)
     *
     * @return bool
     * @throws ConnectionException
     */
    public function isMariaDBGte114(): bool
    {
        $sqlVersion = $this->getSqlVersion();
        $isMariaDB114 = str_contains($sqlVersion, SqlVersionProvider::MARIA_DB_11_4_VERSION);
        $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION);
        if ($isMariaDB114 && version_compare($sqlExactVersion, '11.4.0', '>=')) {
            return true;
        }
        return false;
    }

    /**
     * Check if MariaDB version is 11.8.x (compatibility mapped to 10.6.11 behavior)
     *
     * @return bool
     * @throws ConnectionException
     */
    public function isMariaDBGte118(): bool
    {
        $sqlVersion = $this->getSqlVersion();
        $isMariaDB118 = str_contains($sqlVersion, SqlVersionProvider::MARIA_DB_11_8_VERSION);
        $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION);
        if ($isMariaDB118 && version_compare($sqlExactVersion, '11.8.0', '>=')) {
            return true;
        }
        return false;
    }
}
