<?php

declare(strict_types=1);

namespace App\Framework\Database;

use App\Framework\Async\AsyncPromise;
use App\Framework\Async\AsyncService;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\ValueObjects\SqlQuery;

/**
 * Async Decorator für Database ConnectionInterface
 *
 * Erweitert jede Database Connection um asynchrone Fähigkeiten:
 * - Parallel read queries across replicas
 * - Batch insert/update operations
 * - Non-blocking writes
 * - Parallel data aggregation
 * - Read-ahead caching
 * - Connection pool utilization
 */
final readonly class AsyncDatabaseDecorator implements ConnectionInterface
{
    public function __construct(
        private ConnectionInterface $connection,
        private AsyncService $asyncService
    ) {
    }

    // === STANDARD SYNCHRONOUS INTERFACE ===

    public function execute(SqlQuery $query): int
    {
        return $this->connection->execute($query);
    }

    public function query(SqlQuery $query): ResultInterface
    {
        return $this->connection->query($query);
    }

    public function queryOne(SqlQuery $query): ?array
    {
        return $this->connection->queryOne($query);
    }

    public function queryColumn(SqlQuery $query): array
    {
        return $this->connection->queryColumn($query);
    }

    public function queryScalar(SqlQuery $query): mixed
    {
        return $this->connection->queryScalar($query);
    }

    public function beginTransaction(): void
    {
        $this->connection->beginTransaction();
    }

    public function commit(): void
    {
        $this->connection->commit();
    }

    public function rollback(): void
    {
        $this->connection->rollback();
    }

    public function inTransaction(): bool
    {
        return $this->connection->inTransaction();
    }

    public function lastInsertId(): string
    {
        return $this->connection->lastInsertId();
    }

    public function getPdo(): \PDO
    {
        return $this->connection->getPdo();
    }

    // === ASYNC EXTENSIONS ===

    /**
     * Execute query asynchronously
     */
    public function queryAsync(string $sql, array $parameters = []): AsyncPromise
    {
        return $this->asyncService->promise(
            fn () => $this->connection->query($sql, $parameters)
        );
    }

    /**
     * Execute multiple queries in parallel
     */
    public function queryMultiple(array $queries): array
    {
        $operations = [];
        foreach ($queries as $key => $queryData) {
            $sql = $queryData['sql'] ?? $queryData;
            $params = $queryData['params'] ?? [];

            $operations[$key] = fn () => $this->connection->query($sql, $params);
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Parallel data aggregation from multiple tables
     */
    public function aggregate(array $namedQueries): array
    {
        $operations = [];
        foreach ($namedQueries as $name => $queryData) {
            $operations[$name] = function () use ($queryData) {
                try {
                    $sql = $queryData['sql'] ?? $queryData;
                    $params = $queryData['params'] ?? [];
                    $method = $queryData['method'] ?? 'query';

                    $result = match($method) {
                        'queryOne' => $this->connection->queryOne($sql, $params),
                        'queryColumn' => $this->connection->queryColumn($sql, $params),
                        'queryScalar' => $this->connection->queryScalar($sql, $params),
                        default => $this->connection->query($sql, $params),
                    };

                    return [
                        'success' => true,
                        'data' => $result,
                        'error' => null,
                    ];
                } catch (\Exception $e) {
                    return [
                        'success' => false,
                        'data' => null,
                        'error' => $e->getMessage(),
                    ];
                }
            };
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Batch insert operations
     */
    public function batchInsert(string $table, array $columns, array $rows, int $batchSize = 100): array
    {
        if (empty($rows)) {
            return [];
        }

        $batches = array_chunk($rows, $batchSize);
        $placeholders = '(' . str_repeat('?,', count($columns) - 1) . '?)';

        $operations = [];
        foreach ($batches as $i => $batch) {
            $operations["batch_$i"] = function () use ($table, $columns, $batch, $placeholders) {
                $values = str_repeat($placeholders . ',', count($batch) - 1) . $placeholders;
                $sql = "INSERT INTO `$table` (`" . implode('`, `', $columns) . "`) VALUES $values";

                $params = [];
                foreach ($batch as $row) {
                    $params = array_merge($params, array_values($row));
                }

                return $this->connection->execute($sql, $params);
            };
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Batch update operations
     */
    public function batchUpdate(string $table, array $updates, string $keyColumn = 'id', int $batchSize = 50): array
    {
        if (empty($updates)) {
            return [];
        }

        $batches = array_chunk($updates, $batchSize);

        $operations = [];
        foreach ($batches as $i => $batch) {
            $operations["batch_$i"] = function () use ($table, $batch, $keyColumn) {
                $affected = 0;
                foreach ($batch as $update) {
                    $keyValue = $update[$keyColumn];
                    unset($update[$keyColumn]);

                    $setParts = [];
                    $params = [];
                    foreach ($update as $column => $value) {
                        $setParts[] = "`$column` = ?";
                        $params[] = $value;
                    }
                    $params[] = $keyValue;

                    $sql = "UPDATE `$table` SET " . implode(', ', $setParts) . " WHERE `$keyColumn` = ?";
                    $affected += $this->connection->execute($sql, $params);
                }

                return $affected;
            };
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Parallel read-ahead for related data
     */
    public function readAhead(array $queries, ?Duration $timeout = null): array
    {
        $timeout ??= Duration::fromSeconds(10);

        $operations = [];
        foreach ($queries as $key => $queryData) {
            $operations[$key] = function () use ($queryData, $timeout) {
                return $this->asyncService->withTimeout(function () use ($queryData) {
                    $sql = $queryData['sql'] ?? $queryData;
                    $params = $queryData['params'] ?? [];
                    $method = $queryData['method'] ?? 'query';

                    return match($method) {
                        'queryOne' => $this->connection->queryOne($sql, $params),
                        'queryColumn' => $this->connection->queryColumn($sql, $params),
                        'queryScalar' => $this->connection->queryScalar($sql, $params),
                        default => $this->connection->query($sql, $params),
                    };
                }, $timeout);
            };
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Non-blocking write operations (fire and forget)
     */
    public function executeAsync(string $sql, array $parameters = []): void
    {
        $this->asyncService->async(fn () => $this->connection->execute($sql, $parameters));
    }

    /**
     * Parallel replica queries (if ReadWriteConnection is used)
     */
    public function queryReplicas(string $sql, array $parameters = []): array
    {
        if (! ($this->connection instanceof ReadWriteConnection)) {
            // Fallback to single query
            return [$this->connection->query($sql, $parameters)];
        }

        $readConnections = $this->connection->getReadConnections();
        if (count($readConnections) <= 1) {
            return [$this->connection->query($sql, $parameters)];
        }

        $operations = [];
        foreach ($readConnections as $i => $readConnection) {
            $operations["replica_$i"] = fn () => $readConnection->query($sql, $parameters);
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Optimistic locking with retry
     */
    public function optimisticUpdate(
        string $table,
        array $data,
        array $conditions,
        string $versionColumn = 'version',
        int $maxRetries = 3
    ): bool {
        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            try {
                // Read current version
                $currentData = $this->queryOne(
                    "SELECT * FROM `$table` WHERE " . $this->buildWhereClause($conditions),
                    array_values($conditions)
                );

                if (! $currentData) {
                    return false;
                }

                $currentVersion = $currentData[$versionColumn];
                $newVersion = $currentVersion + 1;

                // Update with version check
                $data[$versionColumn] = $newVersion;
                $updateConditions = array_merge($conditions, [$versionColumn => $currentVersion]);

                $setParts = [];
                $params = [];
                foreach ($data as $column => $value) {
                    $setParts[] = "`$column` = ?";
                    $params[] = $value;
                }

                $whereParts = [];
                foreach ($updateConditions as $column => $value) {
                    $whereParts[] = "`$column` = ?";
                    $params[] = $value;
                }

                $sql = "UPDATE `$table` SET " . implode(', ', $setParts) .
                       " WHERE " . implode(' AND ', $whereParts);

                $affected = $this->connection->execute($sql, $params);

                if ($affected > 0) {
                    return true;
                }

                // Version conflict, retry
                if ($attempt < $maxRetries) {
                    $this->asyncService->delay(Duration::fromMilliseconds(50 * $attempt))->getReturn();
                }

            } catch (\Exception $e) {
                if ($attempt === $maxRetries) {
                    throw $e;
                }
                $this->asyncService->delay(Duration::fromMilliseconds(100 * $attempt))->getReturn();
            }
        }

        return false;
    }

    /**
     * Transaction with timeout
     */
    public function transactionWithTimeout(callable $callback, ?Duration $timeout = null): mixed
    {
        $timeout ??= Duration::fromSeconds(30);

        return $this->asyncService->withTimeout(function () use ($callback) {
            $this->beginTransaction();

            try {
                $result = $callback($this);
                $this->commit();

                return $result;
            } catch (\Exception $e) {
                $this->rollback();

                throw $e;
            }
        }, $timeout);
    }

    /**
     * Parallel table statistics collection
     */
    public function getTableStats(array $tables): array
    {
        $operations = [];
        foreach ($tables as $table) {
            $operations[$table] = function () use ($table) {
                try {
                    $stats = [];

                    // Row count
                    $stats['row_count'] = $this->connection->queryScalar(
                        "SELECT COUNT(*) FROM `$table`"
                    );

                    // Table size (MySQL specific)
                    $sizeResult = $this->connection->queryOne(
                        "SELECT 
                            ROUND(((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024), 2) AS size_mb,
                            DATA_LENGTH,
                            INDEX_LENGTH
                         FROM information_schema.TABLES 
                         WHERE table_schema = DATABASE() AND table_name = ?",
                        [$table]
                    );

                    $stats['size_mb'] = $sizeResult['size_mb'] ?? 0;
                    $stats['data_size'] = $sizeResult['DATA_LENGTH'] ?? 0;
                    $stats['index_size'] = $sizeResult['INDEX_LENGTH'] ?? 0;

                    return $stats;
                } catch (\Exception $e) {
                    return ['error' => $e->getMessage()];
                }
            };
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Get database statistics including async metrics
     */
    public function getStats(): array
    {
        $baseStats = [];
        if (method_exists($this->connection, 'getStatistics')) {
            $baseStats = $this->connection->getStatistics();
        }

        return array_merge($baseStats, [
            'async_enabled' => true,
            'async_stats' => $this->asyncService->getStats(),
            'connection_type' => get_class($this->connection),
            'supports_read_replicas' => $this->connection instanceof ReadWriteConnection,
        ]);
    }

    /**
     * Get the underlying connection
     */
    public function getWrappedConnection(): ConnectionInterface
    {
        return $this->connection;
    }

    /**
     * Build WHERE clause from conditions array
     */
    private function buildWhereClause(array $conditions): string
    {
        $parts = [];
        foreach (array_keys($conditions) as $column) {
            $parts[] = "`$column` = ?";
        }

        return implode(' AND ', $parts);
    }
}
