<?php declare(strict_types = 1);

namespace MailPoet\Migrator;

if (!defined('ABSPATH')) exit;


use MailPoet\Config\Env;
use MailPoet\DI\ContainerWrapper;
use MailPoetVendor\Doctrine\DBAL\Connection;
use MailPoetVendor\Doctrine\DBAL\Exception;
use MailPoetVendor\Doctrine\ORM\EntityManager;

abstract class DbMigration {
  /** @var Connection */
  protected $connection;

  /** @var EntityManager */
  private $entityManager;

  public function __construct(
    ContainerWrapper $container
  ) {
    $this->connection = $container->get(Connection::class);
    $this->entityManager = $container->get(EntityManager::class);
  }

  abstract public function run(): void;

  /**
   * @param class-string<object> $entityClass
   */
  protected function getTableName(string $entityClass): string {
    return $this->entityManager->getClassMetadata($entityClass)->getTableName();
  }

  protected function createTable(string $tableName, array $attributes): void {
    global $wpdb;
    $prefix = Env::$dbPrefix;
    $charsetCollate = $wpdb->get_charset_collate();
    $sql = implode(",\n", $attributes);
    $this->connection->executeStatement("
      CREATE TABLE IF NOT EXISTS {$prefix}{$tableName} (
        $sql
      ) {$charsetCollate};
    ");
  }

  protected function columnExists(string $tableName, string $columnName): bool {
    global $wpdb;
    $suppressErrors = $wpdb->suppress_errors();
    try {
      $this->connection->executeStatement("SELECT $columnName FROM $tableName LIMIT 0");
      return true;
    } catch (Exception $e) {
      return false;
    } finally {
      $wpdb->suppress_errors($suppressErrors);
    }
  }

  protected function tableExists(string $tableName): bool {
    global $wpdb;
    $suppressErrors = $wpdb->suppress_errors();
    try {
      $this->connection->executeStatement("SELECT 1 FROM $tableName LIMIT 0");
      return true;
    } catch (Exception $e) {
      return false;
    } finally {
      $wpdb->suppress_errors($suppressErrors);
    }
  }

  protected function indexExists(string $tableName, string $indexName): bool {
    global $wpdb;
    $suppressErrors = $wpdb->suppress_errors();
    try {
      $this->connection->executeStatement("ALTER TABLE $tableName ADD INDEX $indexName (__non__existent__column__name__)");
    } catch (Exception $e) {
      // Index creating index failed on not existing column we use a fallback. This can happen on MySQL 5.7 and lower and on some MariaDB versions.
      if ($e->getCode() === 1072) {
        $database = $wpdb->dbname;
        $result = $wpdb->get_var($wpdb->prepare(
          "SELECT count(*)
         FROM information_schema.statistics
         WHERE table_schema = COALESCE(DATABASE(), %s)
         AND table_name = %s
         AND index_name = %s",
          $database,
          $tableName,
          $indexName
        ));

        return $result > 0;
      }
      // Index exists when the error message contains its name. Otherwise, it's the non-existent column error.
      return strpos($e->getMessage(), $indexName) !== false;
    } finally {
      $wpdb->suppress_errors($suppressErrors);
    }
    return false;
  }
}
