<?php

declare(strict_types=1);

namespace App\Framework\Database;

use App\Framework\Core\Events\EventDispatcher;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\Config\DatabaseConfig;
use App\Framework\Database\Migration\MigrationLoader;
use App\Framework\Database\Migration\MigrationRunner;
use App\Framework\Database\Platform\DatabasePlatform;
use App\Framework\Database\Profiling\ProfileSummary;
use App\Framework\Database\Profiling\ProfilingConnection;
use App\Framework\Database\Profiling\ProfilingDashboard;
use App\Framework\Database\Profiling\QueryLogger;
use App\Framework\Database\Profiling\QueryProfiler;
use App\Framework\Database\Profiling\SlowQueryDetector;
use App\Framework\Database\ReadWrite\ReplicationLagDetector;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\SystemClock;
use App\Framework\DateTime\Timer;
use App\Framework\Logging\Logger;

final class DatabaseManager
{
    private ?ConnectionPool $connectionPool = null;

    private ?ReadWriteConnection $readWriteConnection = null;

    public function __construct(
        private readonly DatabaseConfig $config,
        private readonly DatabasePlatform $platform,
        private readonly Timer $timer,
        private readonly string $migrationsPath = 'database/migrations',
        private readonly ?Clock $clock = null,
        private readonly ?Logger $logger = null,
        private readonly ?EventDispatcher $eventDispatcher = null
    ) {
    }

    public function getConnection(): ConnectionInterface
    {
        $connection = null;

        if ($this->config->poolConfig->enabled) {
            $connection = $this->getPooledConnection();
        } elseif ($this->config->readWriteConfig->enabled) {
            $connection = $this->getReadWriteConnection();
        } else {
            $connection = DatabaseFactory::createConnection($this->config->driverConfig);
        }

        // Wrap with profiling if enabled
        if ($this->config->profilingConfig->enabled) {
            $connection = $this->wrapWithProfiling($connection);
        }

        return $connection;
    }

    public function getPooledConnection(): PooledConnection
    {
        if ($this->connectionPool === null) {
            $this->connectionPool = new ConnectionPool(
                $this->config->driverConfig,
                $this->config->poolConfig,
                $this->timer
            );
        }

        return $this->connectionPool->getConnection();
    }

    public function getReadWriteConnection(): ReadWriteConnection
    {
        if ($this->readWriteConnection === null) {
            $writeConnection = DatabaseFactory::createConnection($this->config->driverConfig);

            $readConnections = [];
            foreach ($this->config->readWriteConfig->readConnections as $readConfig) {
                $readConnections[] = DatabaseFactory::createConnection($readConfig);
            }

            // Create advanced connection with routing if dependencies are available
            if ($this->clock) {
                $lagDetector = new ReplicationLagDetector($this->clock, $this->timer);

                $this->readWriteConnection = new ReadWriteConnection(
                    $writeConnection,
                    $readConnections,
                    $this->config->readWriteConfig,
                    $this->clock,
                    $lagDetector
                );
            } else {
                // Fallback to simple connection
                $this->readWriteConnection = new ReadWriteConnection($writeConnection, $readConnections);
            }
        }

        return $this->readWriteConnection;
    }

    /**
     * Wrap connection with profiling capabilities
     */
    private function wrapWithProfiling(ConnectionInterface $connection): ProfilingConnection
    {
        if (! $this->clock) {
            throw new \RuntimeException('Clock is required for database profiling');
        }

        // Create profiler
        $profiler = new QueryProfiler(
            $this->clock,
            $this->config->profilingConfig->slowQueryThreshold->toSeconds()
        );

        // Create logger if logging is enabled
        $queryLogger = null;
        if ($this->logger) {
            $queryLogger = new QueryLogger(
                $this->logger,
                $this->config->profilingConfig->slowQueryThreshold,
                $this->config->profilingConfig->logSlowQueriesOnly,
                $this->config->profilingConfig->logParameters,
                $this->config->profilingConfig->logStackTrace,
                $this->config->profilingConfig->maxLoggedQueries
            );
        }

        // Create profiling connection
        $profilingConnection = new ProfilingConnection($connection, $profiler, $queryLogger);

        // Set up slow query detection if enabled
        if ($this->config->profilingConfig->enableSlowQueryDetection &&
            $this->eventDispatcher &&
            $queryLogger) {

            $slowQueryDetector = new SlowQueryDetector(
                $this->eventDispatcher,
                $this->clock,
                $this->config->profilingConfig->slowQueryThreshold,
                Duration::fromSeconds($this->config->profilingConfig->slowQueryThreshold->toSeconds() * 5)
            );

            // Register event listener to detect slow queries from logged queries
            // This would be done in a real application through proper event handling
        }

        return $profilingConnection;
    }

    public function migrate(?string $migrationsPath = null): array
    {
        $migrationsPath = $migrationsPath ?? $this->migrationsPath;

        $loader = new MigrationLoader($migrationsPath);
        $migrations = $loader->loadMigrations();

        $runner = new MigrationRunner(
            connection: $this->getConnection(),
            platform: $this->platform,
            clock: $this->clock ?? new SystemClock(),
            tableConfig: null,
            logger: $this->logger
        );

        return $runner->migrate($migrations);
    }

    public function rollback(?string $migrationsPath = null, int $steps = 1): array
    {
        $migrationsPath = $migrationsPath ?? $this->migrationsPath;

        $loader = new MigrationLoader($migrationsPath);
        $migrations = $loader->loadMigrations();

        $runner = new MigrationRunner(
            connection: $this->getConnection(),
            platform: $this->platform,
            clock: $this->clock ?? new SystemClock(),
            tableConfig: null,
            logger: $this->logger
        );

        return $runner->rollback($migrations, $steps);
    }

    public function getMigrationStatus(?string $migrationsPath = null): array
    {
        $migrationsPath = $migrationsPath ?? $this->migrationsPath;

        $loader = new MigrationLoader($migrationsPath);
        $migrations = $loader->loadMigrations();

        $runner = new MigrationRunner(
            connection: $this->getConnection(),
            platform: $this->platform,
            clock: $this->clock ?? new SystemClock(),
            tableConfig: null,
            logger: $this->logger
        );

        return $runner->getStatus($migrations);
    }

    /**
     * Register this database manager with a profiling dashboard
     */
    public function registerWithProfilingDashboard(ProfilingDashboard $dashboard, string $connectionName = 'default'): void
    {
        $connection = $this->getConnection();

        if ($connection instanceof ProfilingConnection) {
            $dashboard->registerConnection($connectionName, $connection);
        }
    }

    public function hasConnection(): bool
    {
        return $this->connectionPool !== null || $this->readWriteConnection !== null;
    }

    /**
     * @return array<string>
     */
    public function getConnectionNames(): array
    {
        $names = ['default'];

        if ($this->config->readWriteConfig->enabled) {
            $names[] = 'read';
            $names[] = 'write';
        }

        if ($this->config->poolConfig->enabled) {
            for ($i = 0; $i < $this->config->poolConfig->maxConnections; $i++) {
                $names[] = "pool_connection_{$i}";
            }
        }

        return $names;
    }

    /**
     * Get profiling statistics for this database manager
     */
    public function getProfilingStatistics(): ?array
    {
        $connection = $this->getConnection();

        if ($connection instanceof ProfilingConnection) {
            return $connection->getProfilingStatistics();
        }

        return null;
    }

    /**
     * Get profiling summary for this database manager
     */
    public function getProfilingSummary(): ?ProfileSummary
    {
        $connection = $this->getConnection();

        if ($connection instanceof ProfilingConnection) {
            return $connection->getProfilingSummary();
        }

        return null;
    }

    /**
     * Check if profiling is enabled
     */
    public function isProfilingEnabled(): bool
    {
        return $this->config->profilingConfig->enabled;
    }

    public function getConnectionPoolStats(): array
    {
        return $this->connectionPool?->getStats() ?? [];
    }
}
