<?php
/*
 * Copyright (c) 2023. RTM Business
 */

namespace WordpressModels\ORM\Command;

use Doctrine\DBAL\Platforms\MySQL80Platform;
use Doctrine\DBAL\Schema\MySQLSchemaManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Driver\DatabaseDriver;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
use Doctrine\ORM\Tools\EntityGenerator;
use Doctrine\ORM\Tools\Export\ClassMetadataExporter;
use Doctrine\ORM\Tools\Export\Driver\AnnotationExporter;
use Symfony\Component\Console\Application;
use WordpressModels\ORM\EntityManagerFactory;
use WP_CLI;

class DoctrineCommandBridge
{

    private Application $doctrineApplication;

    public function __construct()
    {
        $this->doctrineApplication = ConsoleRunner::createApplication(
            new SingleManagerProvider(EntityManagerFactory::create())
        );
    }

    /**
     * Generate an array of SQL statements to create the database schema for classes in the specified plugin.
     *
     * This is a modified version of the orm:update-schema command.
     *
     * @return void
     * @throws WP_CLI\ExitException
     * @throws \Doctrine\DBAL\Exception
     */
    public function dump_migration(array $args, array $flags)
    {
        $args = array_filter($args, fn($v) => $v !== '--');
        if ($args && ($path = current($args))) {
            $args = ['dest-path' => $path];
        }

        if (!isset($flags['plugin-prefix'])) {
            WP_CLI::error("You must set a plugin table namespace to update schemas");
        }

        try {
            $em = EntityManagerFactory::create();
        } catch (\Throwable $throwable) {
            WP_CLI::error($throwable->getMessage());
        }

        $nonStandardClasses = $this->getNonStandardClassMetadata($em);
        $pluginClasses = array_keys(array_filter(
            apply_filters('doctrine_plugin_entity_table_prefixes', []),
            fn($prefix) => $prefix === $flags['plugin-prefix']));

        WP_CLI::line("Dumping update schema for the following classes, filtered by plugin table prefix {$flags['plugin-prefix']}: \n"
            . join(",\n", $pluginClasses));

        $tool = new SchemaTool($em, $this->getEnhancedSchemaManager($em));

        try {
            global $wpdb;
            $sql = $tool->getUpdateSchemaSql($em->getMetadataFactory()->getAllMetadata(), $wpdb->prefix . $flags['plugin-prefix'] . '_');
        } catch (\Throwable $throwable) {
            WP_CLI::error($throwable->getMessage());
        }

        if (isset($args['dest-path'])) {
            $datetime = new \DateTime();
            $format = $datetime->format(\DateTimeInterface::ATOM);
            $path = $args['dest-path'] . '/schema_update_' . $format . '.sql';

            $dir = dirname($path);
            if (!is_dir($dir)) {
                mkdir($dir, 0775, true);
            }
            file_put_contents($path, "-- SCHEMA UPDATE $format \n\n", FILE_APPEND);

            foreach ($sql as $part) {
                file_put_contents($path, "$part;\n\n", FILE_APPEND);
            }
            chmod($path, 0664);
            WP_CLI::success("Succesfully exported the Update Schema to $path");
        } else {
            foreach ($sql as $part) {
                WP_CLI::line($part);
            }
            WP_CLI::success("Done!");
        }
    }

    /**
     * Dump Entity classes from the current database schema.
     *
     * This is a modified version of the orm:create-entities command, which may or may not work.
     */
    public function dump_entities($args, $flags)
    {
        $args = array_filter($args, fn($v) => $v !== '--');
        if (!$args || !($destPath = current($args))) {
            WP_CLI::error("Missing destination path");
        }

        $namespace = $flags['namespace'] ?? 'Database\\Entities';

        $entityGenerator = new EntityGenerator();
        $entityGenerator->setGenerateAnnotations(true);
        $entityGenerator->setGenerateStubMethods(true);
        $entityGenerator->setRegenerateEntityIfExists(false);
        $entityGenerator->setUpdateEntityIfExists(false);
        $entityGenerator->setNumSpaces(4);

        /** @var AnnotationExporter $annotationExporter */
        $annotationExporter = (new ClassMetadataExporter())->getExporter('annotation');
        $annotationExporter->setEntityGenerator($entityGenerator);

        $entityManager = EntityManagerFactory::create();

        // add the enum type as string (https://github.com/doctrine/dbal/issues/3161#issuecomment-635419448)
        $databaseDriver = new DatabaseDriver($this->getEnhancedSchemaManager($entityManager));
        $entityManager->getConfiguration()->setMetadataDriverImpl($databaseDriver);

        $cmf = new DisconnectedClassMetadataFactory();
        $cmf->setEntityManager($entityManager);
        try {
            $metadata = $cmf->getAllMetadata();
        } catch (\Throwable $throwable) {
            WP_CLI::error($throwable->getMessage());
        }

        if ($metadata) {
            WP_CLI::line(sprintf('Importing mapping information from "<info>%s</info>" entity manager', 'default'));
            foreach ($metadata as $class) {
                $className = $class->name;
                $class->name = $namespace . '\\' . $className;
                $path = $destPath . '/' . str_replace('\\', '.', $className) . '.php';

                WP_CLI::colorize("  > writing %G$path%n \n");
                $code = $annotationExporter->exportClassMetadata($class);
                $dir = dirname($path);
                if (!is_dir($dir)) {
                    mkdir($dir, 0775, true);
                }

                file_put_contents($path, $code);
                chmod($path, 0664);
            }

            WP_CLI::success("Exported " . count($metadata) . ' entities!');
        }
        WP_CLI::error('Database does not have any mapping information.');
    }

    /**
     * @param EntityManagerInterface $em
     * @return ClassMetadata[]
     */
    public function getNonStandardClassMetadata(EntityManagerInterface $em): array
    {
        try {
            $allMetadata = $em->getMetadataFactory()->getAllMetadata();
        } catch (\Throwable $throwable) {
            WP_CLI::error($throwable->getMessage());
        }
        $standardNamespaces = ['WordpressModels\\ORM\\Entity', 'WordpressModels\\ORM\\WooCommerceEntity'];
        return array_filter($allMetadata, function (ClassMetadata $meta) use ($standardNamespaces) {
            return !array_filter($standardNamespaces, fn(string $namespace) => str_starts_with($meta->namespace, $namespace));
        });
    }

    /**
     * @param EntityManagerInterface $entityManager
     * @return DatabaseDriver
     * @throws \Doctrine\DBAL\Exception
     */
    public function getEnhancedSchemaManager(EntityManagerInterface $entityManager): MySQLSchemaManager
    {
        $platform = new MySQL80Platform();
        $platform->registerDoctrineTypeMapping('enum', 'string');

        return new MySQLSchemaManager($entityManager->getConnection(), $platform);
    }

}
