<?php

declare(strict_types=1);

namespace App\Framework\Performance\Repository;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\Query\QueryBuilder;
use App\Framework\Database\Query\QueryBuilderFactory;
use App\Framework\Performance\Entity\PerformanceMetric;
use App\Framework\Performance\PerformanceCategory;
use DateTimeImmutable;

final readonly class PerformanceMetricsRepository
{
    private QueryBuilder $queryBuilder;

    public function __construct(
        private ConnectionInterface $connection,
        private QueryBuilderFactory $queryBuilderFactory
    ) {
        $this->queryBuilder = $this->queryBuilderFactory->create($connection);
    }

    public function save(PerformanceMetric $metric): int
    {
        $data = [
            'operation_id' => $metric->operationId,
            'operation_type' => $metric->operationType,
            'category' => $metric->category->value,
            'migration_version' => $metric->migrationVersion?->toString(),
            'execution_time_ms' => $metric->executionTime->toMilliseconds(),
            'memory_start_bytes' => $metric->memoryStart->toBytes(),
            'memory_end_bytes' => $metric->memoryEnd->toBytes(),
            'memory_peak_bytes' => $metric->memoryPeak->toBytes(),
            'memory_delta_bytes' => $metric->memoryDelta->toBytes(),
            'success' => $metric->success,
            'error_message' => $metric->errorMessage,
            'metadata' => $metric->metadata ? json_encode($metric->metadata) : null,
            'created_at' => $metric->createdAt->format('Y-m-d H:i:s'),
            'updated_at' => $metric->updatedAt?->format('Y-m-d H:i:s'),
        ];

        return $this->queryBuilder
            ->table('performance_metrics')
            ->insert($data);
    }

    public function saveBatch(array $metrics): bool
    {
        if (empty($metrics)) {
            return true;
        }

        $data = array_map(fn (PerformanceMetric $metric) => [
            'operation_id' => $metric->operationId,
            'operation_type' => $metric->operationType,
            'category' => $metric->category->value,
            'migration_version' => $metric->migrationVersion?->toString(),
            'execution_time_ms' => $metric->executionTime->toMilliseconds(),
            'memory_start_bytes' => $metric->memoryStart->toBytes(),
            'memory_end_bytes' => $metric->memoryEnd->toBytes(),
            'memory_peak_bytes' => $metric->memoryPeak->toBytes(),
            'memory_delta_bytes' => $metric->memoryDelta->toBytes(),
            'success' => $metric->success,
            'error_message' => $metric->errorMessage,
            'metadata' => $metric->metadata ? json_encode($metric->metadata) : null,
            'created_at' => $metric->createdAt->format('Y-m-d H:i:s'),
            'updated_at' => $metric->updatedAt?->format('Y-m-d H:i:s'),
        ], $metrics);

        return $this->queryBuilder
            ->table('performance_metrics')
            ->insertBatch($data) > 0;
    }

    public function findById(int $id): ?PerformanceMetric
    {
        $result = $this->queryBuilder
            ->table('performance_metrics')
            ->where('id', '=', $id)
            ->first();

        return $result ? PerformanceMetric::fromArray($result) : null;
    }

    public function findByOperationId(string $operationId): array
    {
        $results = $this->queryBuilder
            ->table('performance_metrics')
            ->where('operation_id', '=', $operationId)
            ->orderBy('created_at', 'DESC')
            ->get();

        return array_map(
            fn (array $row) => PerformanceMetric::fromArray($row),
            $results
        );
    }

    public function findByMigrationVersion(MigrationVersion $version): array
    {
        $results = $this->queryBuilder
            ->table('performance_metrics')
            ->where('migration_version', '=', $version->toString())
            ->orderBy('created_at', 'DESC')
            ->get();

        return array_map(
            fn (array $row) => PerformanceMetric::fromArray($row),
            $results
        );
    }

    public function findByCategory(PerformanceCategory $category, int $limit = 100): array
    {
        $results = $this->queryBuilder
            ->table('performance_metrics')
            ->where('category', '=', $category->value)
            ->orderBy('created_at', 'DESC')
            ->limit($limit)
            ->get();

        return array_map(
            fn (array $row) => PerformanceMetric::fromArray($row),
            $results
        );
    }

    public function findFailures(int $limit = 50): array
    {
        $results = $this->queryBuilder
            ->table('performance_metrics')
            ->where('success', '=', false)
            ->orderBy('created_at', 'DESC')
            ->limit($limit)
            ->get();

        return array_map(
            fn (array $row) => PerformanceMetric::fromArray($row),
            $results
        );
    }

    public function findSlowOperations(Duration $threshold, int $limit = 50): array
    {
        $results = $this->queryBuilder
            ->table('performance_metrics')
            ->where('execution_time_ms', '>', $threshold->toMilliseconds())
            ->orderBy('execution_time_ms', 'DESC')
            ->limit($limit)
            ->get();

        return array_map(
            fn (array $row) => PerformanceMetric::fromArray($row),
            $results
        );
    }

    public function getAverageExecutionTime(PerformanceCategory $category): ?Duration
    {
        $result = $this->queryBuilder
            ->table('performance_metrics')
            ->where('category', '=', $category->value)
            ->where('success', '=', true)
            ->average('execution_time_ms');

        return $result ? Duration::fromMilliseconds((int) $result) : null;
    }

    public function getPerformanceStatistics(DateTimeImmutable $since): array
    {
        $results = $this->queryBuilder
            ->table('performance_metrics')
            ->select([
                'category',
                'COUNT(*) as total_operations',
                'AVG(execution_time_ms) as avg_execution_time',
                'MAX(execution_time_ms) as max_execution_time',
                'MIN(execution_time_ms) as min_execution_time',
                'SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful_operations',
                'AVG(memory_peak_bytes) as avg_memory_peak',
            ])
            ->where('created_at', '>=', $since->format('Y-m-d H:i:s'))
            ->groupBy('category')
            ->get();

        return array_map(function (array $row) {
            return [
                'category' => PerformanceCategory::from($row['category']),
                'total_operations' => (int) $row['total_operations'],
                'avg_execution_time' => Duration::fromMilliseconds((int) $row['avg_execution_time']),
                'max_execution_time' => Duration::fromMilliseconds((int) $row['max_execution_time']),
                'min_execution_time' => Duration::fromMilliseconds((int) $row['min_execution_time']),
                'successful_operations' => (int) $row['successful_operations'],
                'success_rate' => (float) $row['successful_operations'] / (float) $row['total_operations'],
                'avg_memory_peak' => \App\Framework\Core\ValueObjects\Byte::fromBytes((int) $row['avg_memory_peak']),
            ];
        }, $results);
    }

    public function deleteOlderThan(DateTimeImmutable $cutoff): int
    {
        return $this->queryBuilder
            ->table('performance_metrics')
            ->where('created_at', '<', $cutoff->format('Y-m-d H:i:s'))
            ->delete();
    }

    public function count(): int
    {
        return $this->queryBuilder
            ->table('performance_metrics')
            ->count();
    }
}
