<?php

declare(strict_types=1);

namespace App\Framework\Performance;

use App\Framework\Database\Connection;
use App\Framework\DateTime\Clock;

class PerformanceDatabaseWorker
{
    private Connection $connection;
    private Clock $clock;
    private array $config;

    public function __construct(Connection $connection, Clock $clock, array $config = [])
    {
        $this->connection = $connection;
        $this->clock = $clock;
        $this->config = array_merge([
            'batch_size' => 100,
            'create_tables' => true,
            'table_prefix' => 'performance_',
        ], $config);

        if ($this->config['create_tables']) {
            $this->ensureTables();
        }
    }

    public function processLogFile(string $logFile): void
    {
        if (!file_exists($logFile)) {
            return;
        }

        $tempFile = $logFile . '.processing';

        // Datei atomar umbenennen für Processing
        if (!rename($logFile, $tempFile)) {
            return;
        }

        try {
            $this->processFile($tempFile);
            unlink($tempFile); // Erfolgreich verarbeitet
        } catch (\Exception $e) {
            // Bei Fehler Datei zurück umbenennen
            rename($tempFile, $logFile);
            error_log("Performance log processing failed: " . $e->getMessage());
            throw $e;
        }
    }

    private function processFile(string $file): void
    {
        $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (empty($lines)) {
            return;
        }

        $logBatch = [];
        $measurementBatch = [];

        foreach ($lines as $line) {
            $data = json_decode($line, true);
            if (!$data) {
                continue;
            }

            $logId = $this->prepareLogEntry($data, $logBatch);
            $this->prepareMeasurementEntries($data, $logId, $measurementBatch);

            // Batch-Verarbeitung
            if (count($logBatch) >= $this->config['batch_size']) {
                $this->insertBatches($logBatch, $measurementBatch);
                $logBatch = [];
                $measurementBatch = [];
            }
        }

        // Rest verarbeiten
        if (!empty($logBatch)) {
            $this->insertBatches($logBatch, $measurementBatch);
        }
    }

    private function prepareLogEntry(array $data, array &$logBatch): string
    {
        $logId = uniqid('log_', true);

        $logBatch[] = [
            'id' => $logId,
            'timestamp' => $data['timestamp'],
            'request_id' => $data['request_id'],
            'url' => $data['url'],
            'method' => $data['method'],
            'total_time_ms' => $data['performance']['summary']['total_time_ms'],
            'memory_mb' => $data['performance']['summary']['total_memory_mb'],
            'memory_peak_mb' => $data['memory_peak_mb'],
            'marker_count' => $data['performance']['summary']['marker_count'],
            'user_agent' => $data['user_agent'] ?? null,
            'data' => json_encode($data['performance']),
            'context' => json_encode($data['context'] ?? []),
            'server_data' => json_encode($data['server_data'] ?? []),
            'created_at' => $this->clock->now()->format('Y-m-d H:i:s'),
        ];

        return $logId;
    }

    private function prepareMeasurementEntries(array $data, string $logId, array &$measurementBatch): void
    {
        foreach ($data['performance']['measurements'] ?? [] as $label => $measurement) {
            $measurementBatch[] = [
                'log_id' => $logId,
                'category' => $measurement['category'],
                'label' => $label,
                'count' => $measurement['count'],
                'total_time_ms' => $measurement['total_time_ms'],
                'avg_time_ms' => $measurement['avg_time_ms'],
                'min_time_ms' => $measurement['min_time_ms'],
                'max_time_ms' => $measurement['max_time_ms'],
                'total_memory_mb' => $measurement['total_memory_mb'],
                'avg_memory_mb' => $measurement['avg_memory_mb'],
            ];
        }
    }

    private function insertBatches(array $logBatch, array $measurementBatch): void
    {
        $pdo = $this->connection->getPdo();

        $pdo->beginTransaction();

        try {
            // Performance Logs einfügen
            if (!empty($logBatch)) {
                $this->insertPerformanceLogs($pdo, $logBatch);
            }

            // Measurements einfügen
            if (!empty($measurementBatch)) {
                $this->insertMeasurements($pdo, $measurementBatch);
            }

            $pdo->commit();

        } catch (\Exception $e) {
            $pdo->rollBack();
            throw $e;
        }
    }

    private function insertPerformanceLogs(\PDO $pdo, array $batch): void
    {
        $tableName = $this->config['table_prefix'] . 'logs';

        $sql = "INSERT INTO {$tableName} (
            id, timestamp, request_id, url, method, total_time_ms, memory_mb,
            memory_peak_mb, marker_count, user_agent, data, context, server_data, created_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

        $stmt = $pdo->prepare($sql);

        foreach ($batch as $entry) {
            $stmt->execute([
                $entry['id'],
                $entry['timestamp'],
                $entry['request_id'],
                $entry['url'],
                $entry['method'],
                $entry['total_time_ms'],
                $entry['memory_mb'],
                $entry['memory_peak_mb'],
                $entry['marker_count'],
                $entry['user_agent'],
                $entry['data'],
                $entry['context'],
                $entry['server_data'],
                $entry['created_at'],
            ]);
        }
    }

    private function insertMeasurements(\PDO $pdo, array $batch): void
    {
        $tableName = $this->config['table_prefix'] . 'measurements';

        $sql = "INSERT INTO {$tableName} (
            log_id, category, label, count, total_time_ms, avg_time_ms,
            min_time_ms, max_time_ms, total_memory_mb, avg_memory_mb
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

        $stmt = $pdo->prepare($sql);

        foreach ($batch as $entry) {
            $stmt->execute([
                $entry['log_id'],
                $entry['category'],
                $entry['label'],
                $entry['count'],
                $entry['total_time_ms'],
                $entry['avg_time_ms'],
                $entry['min_time_ms'],
                $entry['max_time_ms'],
                $entry['total_memory_mb'],
                $entry['avg_memory_mb'],
            ]);
        }
    }

    private function ensureTables(): void
    {
        $pdo = $this->connection->getPdo();
        $tablePrefix = $this->config['table_prefix'];

        // Performance Logs Tabelle
        $pdo->exec("
            CREATE TABLE IF NOT EXISTS {$tablePrefix}logs (
                id VARCHAR(255) PRIMARY KEY,
                timestamp DATETIME NOT NULL,
                request_id VARCHAR(255) NOT NULL,
                url VARCHAR(500) NOT NULL,
                method VARCHAR(10) NOT NULL,
                total_time_ms DECIMAL(10,2) NOT NULL,
                memory_mb DECIMAL(10,2) NOT NULL,
                memory_peak_mb DECIMAL(10,2) NOT NULL,
                marker_count INT NOT NULL,
                user_agent TEXT,
                data JSON,
                context JSON,
                server_data JSON,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

                INDEX idx_timestamp (timestamp),
                INDEX idx_url (url(100)),
                INDEX idx_method (method),
                INDEX idx_total_time (total_time_ms),
                INDEX idx_request_id (request_id),
                INDEX idx_created_at (created_at)
            )
        ");

        // Measurements Tabelle
        $pdo->exec("
            CREATE TABLE IF NOT EXISTS {$tablePrefix}measurements (
                id BIGINT PRIMARY KEY AUTO_INCREMENT,
                log_id VARCHAR(255) NOT NULL,
                category VARCHAR(50) NOT NULL,
                label VARCHAR(100) NOT NULL,
                count INT NOT NULL,
                total_time_ms DECIMAL(10,2) NOT NULL,
                avg_time_ms DECIMAL(10,2) NOT NULL,
                min_time_ms DECIMAL(10,2) NOT NULL,
                max_time_ms DECIMAL(10,2) NOT NULL,
                total_memory_mb DECIMAL(10,2) NOT NULL,
                avg_memory_mb DECIMAL(10,2) NOT NULL,

                FOREIGN KEY (log_id) REFERENCES {$tablePrefix}logs(id) ON DELETE CASCADE,
                INDEX idx_log_id (log_id),
                INDEX idx_category (category),
                INDEX idx_label (label),
                INDEX idx_avg_time (avg_time_ms)
            )
        ");
    }

    public function getStatistics(): array
    {
        $pdo = $this->connection->getPdo();
        $tablePrefix = $this->config['table_prefix'];

        $stmt = $pdo->query("
            SELECT
                COUNT(*) as total_logs,
                AVG(total_time_ms) as avg_response_time,
                MAX(total_time_ms) as max_response_time,
                AVG(memory_mb) as avg_memory,
                MAX(memory_mb) as max_memory
            FROM {$tablePrefix}logs
            WHERE created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)
        ");

        return $stmt->fetch(\PDO::FETCH_ASSOC) ?: [];
    }

    public function cleanup(int $retentionDays = 30): void
    {
        $pdo = $this->connection->getPdo();
        $tablePrefix = $this->config['table_prefix'];

        $pdo->exec("
            DELETE FROM {$tablePrefix}logs
            WHERE created_at < DATE_SUB(NOW(), INTERVAL {$retentionDays} DAY)
        ");
    }
}
