<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationCollection;
use App\Framework\Database\Migration\MigrationRunner;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\Platform\MySQLPlatform;
use App\Framework\Database\QueryBuilder\QueryBuilder;
use App\Framework\Database\ResultInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DateTime\SystemClock;
use App\Framework\Performance\Entity\PerformanceMetric;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Performance\OperationTracker;
use App\Framework\Performance\PerformanceCategory;
use App\Framework\Performance\Repository\PerformanceMetricsRepository;

describe('Performance Metrics Integration', function () {
    beforeEach(function () {
        $this->connection = new PerformanceMetricsMockConnection();
        $this->platform = new MySQLPlatform();
        $this->clock = new SystemClock();
        $this->memoryMonitor = new MemoryMonitor();
        $this->operationTracker = new OperationTracker($this->clock, $this->memoryMonitor);
        $this->queryBuilderFactory = new PerformanceMetricsMockQueryBuilderFactory();
        $this->performanceMetricsRepository = new PerformanceMetricsRepository(
            $this->connection,
            $this->queryBuilderFactory
        );

        $this->migrationRunner = new MigrationRunner(
            $this->connection,
            $this->platform,
            $this->clock,
            null, // tableConfig
            null, // logger
            $this->operationTracker,
            $this->memoryMonitor,
            null, // performanceReporter
            null, // memoryThresholds
            $this->performanceMetricsRepository
        );
    });

    test('migration runner persists performance metrics for successful migrations', function () {
        $migration = new PerformanceTestMigration();
        $migrations = new MigrationCollection($migration);

        // Set no applied migrations initially
        $this->connection->setAppliedMigrations([]);

        $result = $this->migrationRunner->migrate($migrations);

        expect($result)->toContain('2024_01_01_000000');
        expect($migration->wasExecuted())->toBeTrue();

        // Verify performance metrics were persisted
        $savedMetrics = $this->queryBuilderFactory->getInsertedData('performance_metrics');
        expect($savedMetrics)->toHaveCount(1);

        $metric = $savedMetrics[0];
        expect($metric['operation_type'])->toBe('migration_execution');
        expect($metric['category'])->toBe('DATABASE');
        expect($metric['migration_version'])->toBe('2024_01_01_000000');
        expect($metric['success'])->toBe(true);
        expect($metric['error_message'])->toBeNull();
        expect($metric['metadata'])->toContain('migration_description');
    });

    test('migration runner persists performance metrics for failed migrations', function () {
        $migration = new PerformanceMetricsFailingTestMigration();
        $migrations = new MigrationCollection($migration);

        // Set no applied migrations initially
        $this->connection->setAppliedMigrations([]);

        expect(function () {
            $this->migrationRunner->migrate($migrations);
        })->toThrow(Exception::class);

        // Verify failed migration performance metrics were persisted
        $savedMetrics = $this->queryBuilderFactory->getInsertedData('performance_metrics');
        expect($savedMetrics)->toHaveCount(1);

        $metric = $savedMetrics[0];
        expect($metric['operation_type'])->toBe('migration_execution');
        expect($metric['category'])->toBe('DATABASE');
        expect($metric['migration_version'])->toBe('2024_01_01_000001');
        expect($metric['success'])->toBe(false);
        expect($metric['error_message'])->toContain('Test migration failure');
        expect($metric['metadata'])->toContain('error_type');
    });

    test('migration runner persists performance metrics for rollbacks', function () {
        $migration = new PerformanceTestMigration();
        $migrations = new MigrationCollection($migration);

        // Set migration as already applied
        $this->connection->setAppliedMigrations([
            ['version' => '2024_01_01_000000', 'description' => 'Performance Test Migration'],
        ]);

        $result = $this->migrationRunner->rollback($migrations, 1);

        expect($result)->toContain($migration);

        // Verify rollback performance metrics were persisted
        $savedMetrics = $this->queryBuilderFactory->getInsertedData('performance_metrics');
        expect($savedMetrics)->toHaveCount(1);

        $metric = $savedMetrics[0];
        expect($metric['operation_type'])->toBe('rollback_execution');
        expect($metric['category'])->toBe('DATABASE');
        expect($metric['migration_version'])->toBe('2024_01_01_000000');
        expect($metric['success'])->toBe(true);
        expect($metric['metadata'])->toContain('rollback_steps');
    });

    test('performance metrics repository can query metrics by migration version', function () {
        // Create test metrics
        $version = MigrationVersion::fromTimestamp('2024_01_01_000000');
        $metrics = [
            PerformanceMetric::fromPerformanceSnapshot(
                'migration_test_1',
                'migration_execution',
                PerformanceCategory::DATABASE,
                Duration::fromMilliseconds(1500),
                Byte::fromBytes(50 * 1024 * 1024),
                Byte::fromBytes(55 * 1024 * 1024),
                Byte::fromBytes(60 * 1024 * 1024),
                Byte::fromBytes(5 * 1024 * 1024),
                true,
                null,
                $version,
                ['test' => 'data']
            ),
            PerformanceMetric::fromPerformanceSnapshot(
                'rollback_test_1',
                'rollback_execution',
                PerformanceCategory::DATABASE,
                Duration::fromMilliseconds(800),
                Byte::fromBytes(55 * 1024 * 1024),
                Byte::fromBytes(50 * 1024 * 1024),
                Byte::fromBytes(55 * 1024 * 1024),
                Byte::fromBytes(-5 * 1024 * 1024),
                true,
                null,
                $version,
                ['rollback' => 'data']
            ),
        ];

        // Save metrics
        $this->performanceMetricsRepository->saveBatch($metrics);

        // Query by migration version
        $foundMetrics = $this->performanceMetricsRepository->findByMigrationVersion($version);

        expect($foundMetrics)->toHaveCount(2);
        expect($foundMetrics[0]->operationType)->toBeIn(['migration_execution', 'rollback_execution']);
        expect($foundMetrics[1]->operationType)->toBeIn(['migration_execution', 'rollback_execution']);
    });

    test('performance metrics repository can get performance statistics', function () {
        $since = new DateTimeImmutable('-1 hour');

        // Create test metrics with different categories
        $metrics = [
            PerformanceMetric::fromPerformanceSnapshot(
                'migration_1',
                'migration_execution',
                PerformanceCategory::DATABASE,
                Duration::fromMilliseconds(1000),
                Byte::fromBytes(50 * 1024 * 1024),
                Byte::fromBytes(55 * 1024 * 1024),
                Byte::fromBytes(60 * 1024 * 1024),
                Byte::fromBytes(5 * 1024 * 1024),
                true
            ),
            PerformanceMetric::fromPerformanceSnapshot(
                'migration_2',
                'migration_execution',
                PerformanceCategory::DATABASE,
                Duration::fromMilliseconds(2000),
                Byte::fromBytes(60 * 1024 * 1024),
                Byte::fromBytes(65 * 1024 * 1024),
                Byte::fromBytes(70 * 1024 * 1024),
                Byte::fromBytes(5 * 1024 * 1024),
                false,
                'Test failure'
            ),
        ];

        $this->performanceMetricsRepository->saveBatch($metrics);

        $statistics = $this->performanceMetricsRepository->getPerformanceStatistics($since);

        expect($statistics)->toHaveCount(1); // Only DATABASE category
        expect($statistics[0]['category'])->toBe(PerformanceCategory::DATABASE);
        expect($statistics[0]['total_operations'])->toBe(2);
        expect($statistics[0]['successful_operations'])->toBe(1);
        expect($statistics[0]['success_rate'])->toBe(0.5);
        expect($statistics[0]['avg_execution_time'])->toBeInstanceOf(Duration::class);
    });

    test('performance metrics repository can find slow operations', function () {
        $slowThreshold = Duration::fromMilliseconds(1500);

        $metrics = [
            PerformanceMetric::fromPerformanceSnapshot(
                'fast_migration',
                'migration_execution',
                PerformanceCategory::DATABASE,
                Duration::fromMilliseconds(500), // Fast
                Byte::fromBytes(50 * 1024 * 1024),
                Byte::fromBytes(55 * 1024 * 1024),
                Byte::fromBytes(60 * 1024 * 1024),
                Byte::fromBytes(5 * 1024 * 1024),
                true
            ),
            PerformanceMetric::fromPerformanceSnapshot(
                'slow_migration',
                'migration_execution',
                PerformanceCategory::DATABASE,
                Duration::fromMilliseconds(3000), // Slow
                Byte::fromBytes(60 * 1024 * 1024),
                Byte::fromBytes(70 * 1024 * 1024),
                Byte::fromBytes(75 * 1024 * 1024),
                Byte::fromBytes(10 * 1024 * 1024),
                true
            ),
        ];

        $this->performanceMetricsRepository->saveBatch($metrics);

        $slowOperations = $this->performanceMetricsRepository->findSlowOperations($slowThreshold);

        expect($slowOperations)->toHaveCount(1);
        expect($slowOperations[0]->operationId)->toBe('slow_migration');
        expect($slowOperations[0]->executionTime->toMilliseconds())->toBe(3000);
    });
});

// Test fixtures
class PerformanceTestMigration implements Migration
{
    private bool $executed = false;

    public function up(ConnectionInterface $connection): void
    {
        $this->executed = true;
        // Simulate migration execution
        $connection->execute(SqlQuery::create('CREATE TABLE performance_test (id INT)'));
    }

    public function down(ConnectionInterface $connection): void
    {
        $connection->execute(SqlQuery::create('DROP TABLE performance_test'));
    }

    public function getDescription(): string
    {
        return 'Performance Test Migration';
    }

    public function getVersion(): MigrationVersion
    {
        return MigrationVersion::fromTimestamp('2024_01_01_000000');
    }

    public function wasExecuted(): bool
    {
        return $this->executed;
    }
}

class PerformanceMetricsFailingTestMigration implements Migration
{
    public function up(ConnectionInterface $connection): void
    {
        throw new \Exception('Test migration failure');
    }

    public function down(ConnectionInterface $connection): void
    {
        // Empty
    }

    public function getDescription(): string
    {
        return 'Failing Test Migration';
    }

    public function getVersion(): MigrationVersion
    {
        return MigrationVersion::fromTimestamp('2024_01_01_000001');
    }
}

class PerformanceMetricsMockConnection implements ConnectionInterface
{
    private array $queries = [];

    private array $appliedMigrations = [];

    private bool $inTransaction = false;

    public function setAppliedMigrations(array $migrations): void
    {
        $this->appliedMigrations = $migrations;
    }

    public function execute(SqlQuery $query): int
    {
        $this->queries[] = ['type' => 'execute', 'sql' => $query->sql, 'params' => $query->parameters->toArray()];

        return 1;
    }

    public function query(SqlQuery $query): ResultInterface
    {
        $this->queries[] = ['type' => 'query', 'sql' => $query->sql, 'params' => $query->parameters->toArray()];

        return new PerformanceMetricsMockResult($this->appliedMigrations);
    }

    public function queryOne(SqlQuery $query): ?array
    {
        return null;
    }

    public function queryColumn(SqlQuery $query): array
    {
        $this->queries[] = ['type' => 'queryColumn', 'sql' => $query->sql, 'params' => $query->parameters->toArray()];

        return array_column($this->appliedMigrations, 'version');
    }

    public function queryScalar(SqlQuery $query): mixed
    {
        $this->queries[] = ['type' => 'queryScalar', 'sql' => $query->sql, 'params' => $query->parameters->toArray()];

        // Return '1' for connectivity test (SELECT 1)
        if (str_contains($query->sql, 'SELECT 1')) {
            return '1';
        }

        return null;
    }

    public function beginTransaction(): void
    {
        $this->inTransaction = true;
    }

    public function commit(): void
    {
        $this->inTransaction = false;
    }

    public function rollback(): void
    {
        $this->inTransaction = false;
    }

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

    public function lastInsertId(): string
    {
        return '1';
    }

    public function getPdo(): \PDO
    {
        return new class () extends \PDO {
            public function __construct()
            {
                // Skip parent constructor to avoid actual DB connection
            }

            public function getAttribute(int $attribute): mixed
            {
                return match($attribute) {
                    \PDO::ATTR_DRIVER_NAME => 'mysql',
                    default => null
                };
            }
        };
    }

    public function getQueries(): array
    {
        return $this->queries;
    }
}

class PerformanceMetricsMockResult implements ResultInterface
{
    private array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function fetch(): ?array
    {
        return $this->data[0] ?? null;
    }

    public function fetchAll(): array
    {
        return $this->data;
    }

    public function fetchOne(): ?array
    {
        return $this->data[0] ?? null;
    }

    public function fetchColumn(int $column = 0): array
    {
        return array_column($this->data, $column);
    }

    public function fetchScalar(): mixed
    {
        $row = $this->fetchOne();

        return $row ? array_values($row)[0] : null;
    }

    public function rowCount(): int
    {
        return count($this->data);
    }

    public function getIterator(): \Traversable
    {
        return new \ArrayIterator($this->data);
    }

    public function count(): int
    {
        return count($this->data);
    }
}

class PerformanceMetricsMockQueryBuilderFactory
{
    private array $insertedData = [];

    public function create(ConnectionInterface $connection): QueryBuilder
    {
        return new PerformanceMetricsMockQueryBuilder($this);
    }

    public function recordInsert(string $table, array $data): void
    {
        if (! isset($this->insertedData[$table])) {
            $this->insertedData[$table] = [];
        }
        $this->insertedData[$table][] = $data;
    }

    public function getInsertedData(string $table): array
    {
        return $this->insertedData[$table] ?? [];
    }
}

class PerformanceMetricsMockQueryBuilder implements QueryBuilder
{
    private string $tableName = '';

    private array $wheres = [];

    private string $orderBy = '';

    private ?int $limitValue = null;

    public function __construct(private PerformanceMetricsMockQueryBuilderFactory $factory)
    {
    }

    public function table(string $table): self
    {
        $this->tableName = $table;

        return $this;
    }

    public function select(array $columns): self
    {
        return $this;
    }

    public function where(string $column, string $operator, mixed $value): self
    {
        $this->wheres[] = [$column, $operator, $value];

        return $this;
    }

    public function orderBy(string $column, string $direction = 'ASC'): self
    {
        $this->orderBy = "$column $direction";

        return $this;
    }

    public function limit(int $limit): self
    {
        $this->limitValue = $limit;

        return $this;
    }

    public function groupBy(string $column): self
    {
        return $this;
    }

    public function get(): array
    {
        // Mock return based on table and conditions
        if ($this->tableName === 'performance_metrics') {
            return $this->factory->getInsertedData('performance_metrics');
        }

        return [];
    }

    public function first(): ?array
    {
        $results = $this->get();

        return $results[0] ?? null;
    }

    public function count(): int
    {
        return count($this->get());
    }

    public function average(string $column): ?float
    {
        $results = $this->get();
        if (empty($results)) {
            return null;
        }

        $values = array_column($results, $column);

        return array_sum($values) / count($values);
    }

    public function insert(array $data): int
    {
        $this->factory->recordInsert($this->tableName, $data);

        return 1; // Mock insert ID
    }

    public function insertBatch(array $data): int
    {
        foreach ($data as $row) {
            $this->factory->recordInsert($this->tableName, $row);
        }

        return count($data);
    }

    public function update(array $data): int
    {
        return 1;
    }

    public function delete(): int
    {
        return 1;
    }

    public function toSql(): string
    {
        return "SELECT * FROM {$this->tableName}";
    }

    public function getParameters(): array
    {
        return [];
    }

    public function execute(): mixed
    {
        return $this->get();
    }
}
