<?php

declare(strict_types=1);

namespace App\Framework\Database\Migration\Services;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationCollection;
use App\Framework\Database\Migration\MigrationVersionCollection;
use App\Framework\Database\Platform\DatabasePlatform;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;

final readonly class MigrationValidator
{
    public function __construct(
        private ConnectionInterface $connection,
        private DatabasePlatform $platform
    ) {
    }

    public function runPreFlightChecks(MigrationCollection $migrations, MigrationVersionCollection $appliedVersions): array
    {
        $results = [];
        $results['connectivity'] = $this->checkDatabaseConnectivity();
        $results['permissions'] = $this->checkDatabasePermissions();
        $results['schema_state'] = $this->checkSchemaState($migrations, $appliedVersions);
        $results['disk_space'] = $this->checkDiskSpace();
        $results['backup_readiness'] = $this->checkBackupReadiness();
        $results['migration_conflicts'] = $this->checkMigrationConflicts($migrations);
        $results['environment_safety'] = $this->checkEnvironmentSafety();

        return $results;
    }

    public function validateMigrationDependencies(MigrationCollection $migrations, MigrationVersionCollection $appliedVersions): array
    {
        $orphanedDependencies = [];
        $partialChains = $this->findPartialDependencyChains($migrations, $appliedVersions);
        $orderingIssues = $this->validateMigrationOrdering($migrations, $appliedVersions);

        return [
            'orphaned_dependencies' => $orphanedDependencies,
            'partial_chains' => $partialChains,
            'ordering_issues' => $orderingIssues,
        ];
    }

    public function validateRollbackSafety(Migration $migration, string $version): void
    {
        // Basic rollback safety checks
        if (! method_exists($migration, 'down') || ! is_callable([$migration, 'down'])) {
            throw FrameworkException::create(
                ErrorCode::DB_MIGRATION_FAILED,
                "Migration {$version} does not implement rollback functionality"
            );
        }

        // Additional safety checks could be added here
        // - Check for data loss operations
        // - Validate rollback dependencies
        // - Check for irreversible operations
    }

    private function checkDatabaseConnectivity(): array
    {
        try {
            $result = $this->connection->queryScalar(SqlQuery::create('SELECT 1'));

            return [
                'status' => 'pass',
                'message' => 'Database connection successful',
                'details' => ['test_query_result' => $result],
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'message' => 'Database connection failed',
                'details' => ['error' => $e->getMessage()],
                'severity' => 'critical',
            ];
        }
    }

    private function checkDatabasePermissions(): array
    {
        $permissions = [];
        $requiredPermissions = ['CREATE', 'ALTER', 'DROP', 'INSERT', 'UPDATE', 'DELETE', 'SELECT'];

        foreach ($requiredPermissions as $permission) {
            try {
                // Platform-specific permission checks would go here
                $permissions[$permission] = true;
            } catch (\Throwable $e) {
                $permissions[$permission] = false;
            }
        }

        $missingPermissions = array_keys(array_filter($permissions, fn ($has) => ! $has));

        return [
            'status' => empty($missingPermissions) ? 'pass' : 'fail',
            'message' => empty($missingPermissions) ? 'All required permissions available' : 'Missing required permissions',
            'details' => [
                'permissions' => $permissions,
                'missing' => $missingPermissions,
            ],
            'severity' => empty($missingPermissions) ? 'info' : 'critical',
        ];
    }

    private function checkSchemaState(MigrationCollection $migrations, MigrationVersionCollection $appliedVersions): array
    {
        try {
            $expectedTables = $this->extractExpectedTablesFromMigrations($migrations, $appliedVersions);
            $actualTables = $this->getActualDatabaseTables();
            $missingTables = array_diff($expectedTables, $actualTables);
            $unexpectedTables = array_diff($actualTables, $expectedTables);

            $status = empty($missingTables) && empty($unexpectedTables) ? 'pass' : 'warning';

            return [
                'status' => $status,
                'message' => 'Schema state analysis completed',
                'details' => [
                    'expected_tables' => $expectedTables,
                    'actual_tables' => $actualTables,
                    'missing_tables' => $missingTables,
                    'unexpected_tables' => $unexpectedTables,
                ],
                'severity' => $status === 'pass' ? 'info' : 'warning',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'message' => 'Schema state check failed',
                'details' => ['error' => $e->getMessage()],
                'severity' => 'warning',
            ];
        }
    }

    private function checkDiskSpace(): array
    {
        try {
            $freeSpaceBytes = disk_free_space('.');
            $totalSpaceBytes = disk_total_space('.');

            if ($freeSpaceBytes === false || $totalSpaceBytes === false) {
                throw new \RuntimeException('Unable to determine disk space');
            }

            $freeSpace = Byte::fromBytes($freeSpaceBytes);
            $totalSpace = Byte::fromBytes($totalSpaceBytes);
            $usagePercentage = $totalSpaceBytes > 0 ? (1 - ($freeSpaceBytes / $totalSpaceBytes)) * 100 : 0;

            $status = $usagePercentage < 90 ? 'pass' : ($usagePercentage < 95 ? 'warning' : 'fail');

            return [
                'status' => $status,
                'message' => 'Disk space check completed',
                'details' => [
                    'free_space_bytes' => $freeSpaceBytes,
                    'total_space_bytes' => $totalSpaceBytes,
                    'usage_percentage' => round($usagePercentage, 2),
                    'free_space_human' => $freeSpace->toHumanReadable(),
                    'total_space_human' => $totalSpace->toHumanReadable(),
                ],
                'severity' => $status === 'pass' ? 'info' : ($status === 'warning' ? 'warning' : 'critical'),
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'message' => 'Disk space check failed',
                'details' => ['error' => $e->getMessage()],
                'severity' => 'warning',
            ];
        }
    }

    private function checkBackupReadiness(): array
    {
        // This is a placeholder - actual backup systems would be checked here
        return [
            'status' => 'pass',
            'message' => 'Backup readiness not implemented - manual verification required',
            'details' => ['recommendation' => 'Ensure database backup is current before proceeding'],
            'severity' => 'info',
        ];
    }

    private function checkMigrationConflicts(MigrationCollection $migrations): array
    {
        $versions = [];
        $conflicts = [];

        foreach ($migrations as $migration) {
            $version = $migration->getVersion()->toString();
            if (in_array($version, $versions)) {
                $conflicts[] = $version;
            }
            $versions[] = $version;
        }

        return [
            'status' => empty($conflicts) ? 'pass' : 'fail',
            'message' => empty($conflicts) ? 'No migration conflicts found' : 'Migration version conflicts detected',
            'details' => [
                'total_migrations' => count($migrations),
                'unique_versions' => count(array_unique($versions)),
                'conflicting_versions' => $conflicts,
            ],
            'severity' => empty($conflicts) ? 'info' : 'critical',
        ];
    }

    private function checkEnvironmentSafety(): array
    {
        $environment = $this->getEnvironmentDetails();
        $isProduction = in_array($environment['app_env'] ?? 'unknown', ['production', 'prod']);

        return [
            'status' => 'pass',
            'message' => 'Environment safety check completed',
            'details' => [
                'environment' => $environment,
                'is_production' => $isProduction,
                'recommendations' => $isProduction ? ['Create backup', 'Schedule maintenance window'] : [],
            ],
            'severity' => $isProduction ? 'warning' : 'info',
        ];
    }

    private function findPartialDependencyChains(MigrationCollection $migrations, MigrationVersionCollection $appliedVersions): array
    {
        // Placeholder for dependency chain analysis
        return [];
    }

    private function validateMigrationOrdering(MigrationCollection $migrations, MigrationVersionCollection $appliedVersions): array
    {
        $issues = [];
        $versions = [];

        foreach ($migrations as $migration) {
            $version = $migration->getVersion()->toString();
            $timestamp = $this->extractTimestampFromVersion($version);
            $versions[] = ['version' => $version, 'timestamp' => $timestamp];
        }

        // Sort by timestamp and check if versions are in chronological order
        usort($versions, fn ($a, $b) => $a['timestamp'] <=> $b['timestamp']);

        $previousTimestamp = 0;
        foreach ($versions as $versionInfo) {
            if ($versionInfo['timestamp'] < $previousTimestamp) {
                $issues[] = [
                    'version' => $versionInfo['version'],
                    'issue' => 'Version timestamp is out of chronological order',
                    'timestamp' => $versionInfo['timestamp'],
                ];
            }
            $previousTimestamp = $versionInfo['timestamp'];
        }

        return $issues;
    }

    private function extractTimestampFromVersion(string $version): int
    {
        // Extract timestamp from version format: YYYY_MM_DD_HHMMSS_Description
        if (preg_match('/^(\d{4})_(\d{2})_(\d{2})_(\d{6})/', $version, $matches)) {
            $year = (int) $matches[1];
            $month = (int) $matches[2];
            $day = (int) $matches[3];
            $time = $matches[4];
            $hour = (int) substr($time, 0, 2);
            $minute = (int) substr($time, 2, 2);
            $second = (int) substr($time, 4, 2);

            return mktime($hour, $minute, $second, $month, $day, $year);
        }

        return 0;
    }

    private function extractExpectedTablesFromMigrations(MigrationCollection $migrations, MigrationVersionCollection $appliedVersions): array
    {
        // This would analyze migration files to extract expected table names
        // For now, return empty array as placeholder
        return [];
    }

    private function getActualDatabaseTables(): array
    {
        try {
            $query = $this->platform->getListTablesSQL();
            $result = $this->connection->queryColumn(SqlQuery::create($query));

            return $result;
        } catch (\Throwable $e) {
            return [];
        }
    }

    private function getEnvironmentDetails(): array
    {
        return [
            'app_env' => $_ENV['APP_ENV'] ?? 'unknown',
            'php_version' => PHP_VERSION,
            'server_name' => $_SERVER['SERVER_NAME'] ?? 'unknown',
            'database_driver' => $this->platform->getName(),
        ];
    }
}
