<?php

declare(strict_types=1);

namespace App\Framework\Database\Migration;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Services\MigrationDatabaseManager;
use App\Framework\Database\Migration\Services\MigrationErrorAnalyzer;
use App\Framework\Database\Migration\Services\MigrationLogger;
use App\Framework\Database\Migration\Services\MigrationPerformanceTracker;
use App\Framework\Database\Migration\Services\MigrationValidator;
use App\Framework\Database\Migration\ValueObjects\MemoryThresholds;
use App\Framework\Database\Migration\ValueObjects\MigrationTableConfig;
use App\Framework\Database\Platform\DatabasePlatform;
use App\Framework\DateTime\Clock;
use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
use App\Framework\Logging\Logger;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Performance\OperationTracker;
use App\Framework\Performance\PerformanceReporter;
use App\Framework\Performance\Repository\PerformanceMetricsRepository;
use App\Framework\Ulid\UlidGenerator;

final readonly class MigrationRunner
{
    private MigrationDependencyGraph $dependencyGraph;

    private MigrationDatabaseManager $databaseManager;

    private MigrationPerformanceTracker $performanceTracker;

    private MigrationLogger $migrationLogger;

    private MigrationValidator $validator;

    private MigrationErrorAnalyzer $errorAnalyzer;

    public function __construct(
        private ConnectionInterface $connection,
        private DatabasePlatform $platform,
        private Clock $clock,
        private UlidGenerator $ulidGenerator,
        ?MigrationTableConfig $tableConfig = null,
        ?Logger $logger = null,
        ?OperationTracker $operationTracker = null,
        ?MemoryMonitor $memoryMonitor = null,
        ?PerformanceReporter $performanceReporter = null,
        ?MemoryThresholds $memoryThresholds = null,
        ?PerformanceMetricsRepository $performanceMetricsRepository = null
    ) {
        $this->dependencyGraph = new MigrationDependencyGraph();

        $effectiveTableConfig = $tableConfig ?? MigrationTableConfig::default();

        // Initialize service classes
        $this->databaseManager = new MigrationDatabaseManager(
            $this->connection,
            $this->platform,
            $this->clock,
            $effectiveTableConfig
        );

        $this->performanceTracker = new MigrationPerformanceTracker(
            $operationTracker,
            $memoryMonitor,
            $performanceMetricsRepository,
            $logger,
            $memoryThresholds
        );

        $this->migrationLogger = new MigrationLogger($logger);

        $this->validator = new MigrationValidator(
            $this->connection,
            $this->platform
        );

        $this->errorAnalyzer = new MigrationErrorAnalyzer();
    }

    /**
     * Run migrations
     */
    public function migrate(MigrationCollection $migrations, bool $skipPreflightChecks = false): array
    {
        $this->databaseManager->ensureMigrationsTable();

        $executedMigrations = [];
        $appliedVersions = MigrationVersionCollection::fromStrings($this->databaseManager->getAppliedVersions());

        // Pre-flight checks
        if (! $skipPreflightChecks) {
            $this->runPreFlightChecks($migrations, $appliedVersions);
        }

        // Filter and order migrations
        $pendingMigrations = $migrations->filterByNotApplied($appliedVersions);
        if ($pendingMigrations->isEmpty()) {
            $this->migrationLogger->logMigration('none', 'No pending migrations', 'Skipped');

            return [];
        }

        $this->dependencyGraph->buildGraph($pendingMigrations);
        $orderedMigrations = $this->dependencyGraph->getExecutionOrder();
        $totalMigrations = $orderedMigrations->count();

        // Start batch tracking
        $batchOperationId = 'migration_batch_' . $this->ulidGenerator->generate();
        $this->performanceTracker->startBatchOperation($batchOperationId, $totalMigrations);

        $currentPosition = 0;
        foreach ($orderedMigrations as $migration) {
            $version = $migration->getVersion()->toString();
            $currentPosition++;

            if ($appliedVersions->contains($migration->getVersion())) {
                continue;
            }

            try {
                // Start individual migration tracking
                $migrationOperationId = "migration_{$version}";
                $this->performanceTracker->startMigrationOperation(
                    $migrationOperationId,
                    $version,
                    $migration->getDescription(),
                    $currentPosition,
                    $totalMigrations,
                    $batchOperationId
                );

                // Check memory thresholds before migration
                $this->performanceTracker->checkMemoryThresholds($version, $currentPosition - 1, $totalMigrations);

                // Execute migration in transaction
                $this->connection->beginTransaction();

                try {
                    $migration->up($this->connection);
                    $this->databaseManager->recordMigrationExecution($migration, $version);

                    if ($this->connection->inTransaction()) {
                        $this->connection->commit();
                    }

                    // Update operation with successful execution
                    $this->performanceTracker->updateBatchOperation($batchOperationId, [
                        'items_processed' => $currentPosition,
                    ]);
                } catch (\Throwable $e) {
                    if ($this->connection->inTransaction()) {
                        $this->connection->rollback();
                    }

                    throw $e;
                }

                $executedMigrations[] = $version;

                // Complete individual migration tracking
                $migrationSnapshot = $this->performanceTracker->completeMigrationOperation(
                    $migrationOperationId,
                    $version,
                    $migration->getDescription(),
                    $currentPosition,
                    $totalMigrations,
                    $batchOperationId
                );

                $this->migrationLogger->logMigration($version, $migration->getDescription(), 'Migrated');
                $this->migrationLogger->logMigrationProgress($version, $currentPosition, $totalMigrations, 'completed', [
                    'execution_time' => $migrationSnapshot?->duration?->toSeconds(),
                    'memory_used' => $migrationSnapshot?->memoryDelta?->toHumanReadable(),
                ]);

                // Post-migration memory check
                $this->performanceTracker->logPostMigrationMemory($version, $migrationSnapshot);

            } catch (\Throwable $e) {
                // Track failed individual migration
                $failedSnapshot = $this->performanceTracker->failMigrationOperation(
                    $migrationOperationId,
                    $version,
                    $migration->getDescription(),
                    $currentPosition,
                    $totalMigrations,
                    $batchOperationId,
                    $e
                );

                // Track failed batch
                $this->performanceTracker->failBatchOperation($batchOperationId, $e);

                // Enhanced error reporting with recovery hints
                $recoveryHints = $this->errorAnalyzer->generateMigrationRecoveryHints($migration, $e, $executedMigrations);
                $migrationContext = $this->errorAnalyzer->analyzeMigrationContext($migration, $version, $e);

                throw FrameworkException::create(
                    DatabaseErrorCode::MIGRATION_FAILED,
                    "Migration {$version} failed: {$e->getMessage()}"
                )->withContext(
                    ExceptionContext::forOperation('migration.execute', 'MigrationRunner')
                        ->withData([
                            'migration_version' => $version,
                            'migration_description' => $migration->getDescription(),
                            'executed_migrations' => $executedMigrations,
                            'total_migrations' => $totalMigrations,
                            'current_position' => $currentPosition,
                        ])
                        ->withDebug($migrationContext)
                        ->withMetadata([
                            'recovery_hints' => $recoveryHints,
                            'can_retry' => $this->errorAnalyzer->canRetryMigration($e),
                            'rollback_recommended' => $this->errorAnalyzer->isRollbackRecommended($e, $executedMigrations),
                        ])
                );
            }
        }

        // Complete batch tracking
        $batchSnapshot = $this->performanceTracker->completeBatchOperation($batchOperationId);
        $this->migrationLogger->logMigrationBatchSummary($executedMigrations, $totalMigrations, $batchSnapshot);

        return $executedMigrations;
    }

    /**
     * Rollback migrations (only SafelyReversible migrations)
     *
     * This method will ONLY rollback migrations that implement the SafelyReversible interface,
     * ensuring no data loss occurs during rollback operations.
     *
     * @param MigrationCollection $migrations Available migrations
     * @param int $steps Number of migrations to rollback
     * @return array<Migration> Successfully rolled back migrations
     * @throws FrameworkException If attempting to rollback unsafe migration
     */
    public function rollback(MigrationCollection $migrations, int $steps = 1): array
    {
        $appliedVersions = MigrationVersionCollection::fromStrings($this->databaseManager->getAppliedVersions());

        if ($appliedVersions->isEmpty()) {
            $this->migrationLogger->logMigration('none', 'No migrations to rollback', 'Skipped');

            return [];
        }

        $versionsToRollback = array_slice(array_reverse($appliedVersions->toArray()), 0, $steps);
        $rolledBackMigrations = [];
        $totalRollbacks = count($versionsToRollback);

        // Start rollback batch tracking
        $rollbackBatchId = 'rollback_batch_' . $this->ulidGenerator->generate();
        $this->performanceTracker->startBatchOperation($rollbackBatchId, $totalRollbacks);

        $currentPosition = 0;
        foreach ($versionsToRollback as $version) {
            $currentPosition++;
            $migration = $migrations->findByVersion(MigrationVersion::fromTimestamp($version));

            if (! $migration) {
                $this->migrationLogger->logRollbackSkipped($version, ['reason' => 'Migration class not found']);

                continue;
            }

            // CRITICAL SAFETY CHECK: Ensure migration supports safe rollback
            if (! $migration instanceof SafelyReversible) {
                throw FrameworkException::create(
                    DatabaseErrorCode::MIGRATION_NOT_REVERSIBLE,
                    "Migration {$version} does not support safe rollback"
                )->withContext(
                    ExceptionContext::forOperation('migration.rollback', 'MigrationRunner')
                        ->withData([
                            'migration_version' => $version,
                            'migration_class' => get_class($migration),
                            'migration_description' => $migration->getDescription(),
                            'requested_rollback_steps' => $steps,
                            'current_position' => $currentPosition,
                        ])
                        ->withMetadata([
                            'reason' => 'Migration does not implement SafelyReversible interface',
                            'recommendation' => 'Create a new forward migration to undo the changes instead of rolling back',
                            'safe_rollback_guide' => 'See SafelyReversible interface documentation for guidelines',
                        ])
                );
            }

            try {
                // Validate rollback safety
                $this->validator->validateRollbackSafety($migration, $version);

                // Real-time rollback progress tracking
                $this->migrationLogger->logRollbackProgress($version, $currentPosition, $totalRollbacks, 'starting');

                // Start individual rollback tracking
                $rollbackOperationId = "rollback_{$version}";
                $this->performanceTracker->startRollbackOperation(
                    $rollbackOperationId,
                    $version,
                    $migration->getDescription(),
                    $currentPosition,
                    $totalRollbacks,
                    $rollbackBatchId,
                    $steps
                );

                // Execute rollback in transaction
                $this->connection->beginTransaction();

                try {
                    $migration->down($this->connection);
                    $this->databaseManager->recordMigrationRollback($version);

                    if ($this->connection->inTransaction()) {
                        $this->connection->commit();
                    }
                } catch (\Throwable $e) {
                    if ($this->connection->inTransaction()) {
                        $this->connection->rollback();
                    }

                    throw $e;
                }

                $rolledBackMigrations[] = $migration;

                // Complete individual rollback tracking
                $rollbackSnapshot = $this->performanceTracker->completeRollbackOperation(
                    $rollbackOperationId,
                    $version,
                    $migration->getDescription(),
                    $currentPosition,
                    $totalRollbacks,
                    $rollbackBatchId,
                    $steps
                );

                $this->migrationLogger->logMigration($version, $migration->getDescription(), 'Rolled back');
                $this->migrationLogger->logRollbackProgress($version, $currentPosition, $totalRollbacks, 'completed', [
                    'execution_time' => $rollbackSnapshot?->duration?->toSeconds(),
                    'memory_used' => $rollbackSnapshot?->memoryDelta?->toHumanReadable(),
                ]);

                // Post-rollback memory check
                $this->performanceTracker->logPostRollbackMemory($version, $rollbackSnapshot);

            } catch (\Throwable $e) {
                // Generate rollback recovery hints
                $remainingRollbacks = array_slice($versionsToRollback, $currentPosition);
                $recoveryHints = $this->errorAnalyzer->generateRollbackRecoveryHints($migration, $e, $remainingRollbacks);

                throw FrameworkException::create(
                    DatabaseErrorCode::MIGRATION_ROLLBACK_FAILED,
                    "Rollback failed for migration {$version}: {$e->getMessage()}"
                )->withContext(
                    ExceptionContext::forOperation('migration.rollback', 'MigrationRunner')
                        ->withData([
                            'migration_version' => $version,
                            'rollback_steps' => $steps,
                            'completed_rollbacks' => count($rolledBackMigrations),
                            'failed_migration' => $version,
                        ])
                        ->withMetadata([
                            'recovery_hints' => $recoveryHints,
                            'can_continue' => $this->errorAnalyzer->canContinueRollback($remainingRollbacks),
                        ])
                );
            }
        }

        // Complete rollback batch tracking
        $rollbackSnapshot = $this->performanceTracker->completeBatchOperation($rollbackBatchId);
        $this->migrationLogger->logRollbackBatchSummary($rolledBackMigrations, $totalRollbacks, $rollbackSnapshot);

        return $rolledBackMigrations;
    }

    /**
     * Get applied migration versions
     */
    public function getAppliedVersions(): array
    {
        return $this->databaseManager->getAppliedVersions();
    }

    /**
     * Get status information for all migrations
     *
     * @return array<MigrationStatus>
     */
    public function getStatus(MigrationCollection $migrations): array
    {
        $appliedVersions = MigrationVersionCollection::fromStrings($this->databaseManager->getAppliedVersions());
        $statuses = [];

        foreach ($migrations as $migration) {
            $version = $migration->getVersion();
            $applied = $appliedVersions->contains($version);

            $statuses[] = new MigrationStatus(
                version: $version,
                description: $migration->getDescription(),
                applied: $applied
            );
        }

        return $statuses;
    }

    private function runPreFlightChecks(MigrationCollection $migrations, MigrationVersionCollection $appliedVersions): void
    {
        $results = $this->validator->runPreFlightChecks($migrations, $appliedVersions);
        $dependencyResults = $this->validator->validateMigrationDependencies($migrations, $appliedVersions);

        // Extract critical issues and warnings
        $criticalIssues = [];
        $warnings = [];

        foreach ($results as $check => $result) {
            if ($result['status'] === 'fail' && ($result['severity'] ?? 'info') === 'critical') {
                $criticalIssues[] = $check . ': ' . $result['message'];
            } elseif ($result['status'] === 'warning' || ($result['severity'] ?? 'info') === 'warning') {
                $warnings[] = $check . ': ' . $result['message'];
            }
        }

        $this->migrationLogger->logPreFlightResults($results, $criticalIssues, $warnings);
        $this->migrationLogger->logDependencyValidationIssues(
            $dependencyResults['orphaned_dependencies'],
            $dependencyResults['partial_chains'],
            $dependencyResults['ordering_issues']
        );

        // Throw exception if critical issues found
        if (! empty($criticalIssues)) {
            throw FrameworkException::create(
                DatabaseErrorCode::MIGRATION_PREFLIGHT_FAILED,
                'Pre-flight checks failed with critical issues'
            )->withData([
                'critical_issues' => $criticalIssues,
                'warnings' => $warnings,
                'full_results' => $results,
            ]);
        }
    }
}
