<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

class PerformanceMonitor
{
    /**
     * Initial performance markers
     */
    private float $startTime;

    private int $startMemory;

    private int $startPeakMemory;

    /**
     * Output buffer
     */
    private string $buffer = '';

    /**
     * ANSI color codes for console output
     */
    private const COLORS = [
        'green'   => "\033[32m",
        'yellow'  => "\033[33m",
        'magenta' => "\033[35m",
        'cyan'    => "\033[36m",
        'gray'    => "\033[90m",
        'reset'   => "\033[0m",
    ];

    /**
     * Performance thresholds (in milliseconds)
     */
    private const SLOW_REQUEST_THRESHOLD = 1000;

    private const SLOW_QUERY_THRESHOLD = 100;

    public function __construct()
    {
        $this->startTime       = defined('LARAVEL_START') ? LARAVEL_START : microtime(true);
        $this->startMemory     = memory_get_usage();
        $this->startPeakMemory = memory_get_peak_usage();
    }

    public function handle(Request $request, Closure $next): Response
    {
        if (app()->environment('local', 'development')) {
            DB::enableQueryLog();
        }

        $response = $next($request);

        if (app()->environment('local', 'development')) {
            $this->logPerformanceMetrics($request);
            $this->flushBuffer();
        }

        return $response;
    }

    private function gatherMetrics(): array
    {
        $memoryLimit = $this->getMemoryLimit();

        return [
            'time'             => $this->getExecutionTime(),
            'memory'           => $this->getMemoryUsage(),
            'peakMemory'       => $this->getPeakMemoryUsage(),
            'memoryLimit'      => $memoryLimit,
            'memoryPercentage' => $this->calculateMemoryPercentage($memoryLimit),
            'queries'          => $this->getQueryMetrics(),
            'cpu'              => $this->getCpuMetrics(),
            'cache'            => $this->getCacheMetrics(),
        ];
    }

    private function getExecutionTime(): float
    {
        return (microtime(true) - $this->startTime) * 1000;
    }

    private function getMemoryUsage(): float
    {
        return (memory_get_usage() - $this->startMemory) / 1024 / 1024;
    }

    private function getPeakMemoryUsage(): float
    {
        return (memory_get_peak_usage() - $this->startPeakMemory) / 1024 / 1024;
    }

    private function getMemoryLimit(): int
    {
        $limit = ini_get('memory_limit');

        if (! $limit) {
            return 0;
        }

        preg_match('/^(\d+)(K|M|G)?$/i', $limit, $matches);

        $value = (int) data_get($matches, 1, 0);
        $unit  = strtoupper(data_get($matches, 2, ''));

        return match ($unit) {
            'G'     => $value * 1024 * 1024 * 1024,
            'M'     => $value * 1024 * 1024,
            'K'     => $value * 1024,
            default => $value,
        };
    }

    private function calculateMemoryPercentage(int $limit): float
    {
        if ($limit === 0) {
            return 0.0;
        }

        return (memory_get_usage(true) / $limit) * 100;
    }

    private function getQueryMetrics(): ?array
    {
        $queries = DB::getQueryLog();

        if (empty($queries)) {
            return null;
        }

        $totalTime = 0;

        $formattedQueries = [];

        foreach ($queries as $query) {
            $totalTime += data_get($query, 'time');

            if (data_get($query, 'time') > self::SLOW_QUERY_THRESHOLD) {
                $formattedQueries[] = [
                    'sql'  => $this->formatSql(data_get($query, 'query'), data_get($query, 'bindings')),
                    'time' => data_get($query, 'time'),
                ];
            }
        }

        return [
            'count'        => count($queries),
            'time'         => $totalTime,
            'slow_queries' => $formattedQueries,
        ];
    }

    private function formatSql(string $sql, array $bindings): string
    {
        return vsprintf(str_replace(['%', '?'], ['%%', "'%s'"], $sql), $bindings);
    }

    private function getCpuMetrics(): ?array
    {
        if (! function_exists('getrusage')) {
            return null;
        }

        $usage = getrusage();

        return [
            'user'   => (data_get($usage, 'ru_utime.tv_sec') * 1000 + intval(data_get($usage, 'ru_utime.tv_usec') / 1000)),
            'system' => (data_get($usage, 'ru_stime.tv_sec') * 1000 + intval(data_get($usage, 'ru_stime.tv_usec') / 1000)),
        ];
    }

    private function getCacheMetrics(): ?array
    {
        if (! function_exists('opcache_get_status')) {
            return null;
        }

        $status = opcache_get_status(false);

        if (! $status) {
            return null;
        }

        $stats  = data_get($status, 'opcache_statistics');
        $hits   = data_get($stats, 'hits');
        $misses = data_get($stats, 'misses');
        $total  = $hits + $misses;

        return [
            'hit_rate'     => $total > 0 ? ($hits / $total * 100) : 0,
            'memory_usage' => data_get($status, 'memory_usage'),
        ];
    }

    private function appendToBuffer(string $text): void
    {
        $this->buffer .= $text;
    }

    private function flushBuffer(): void
    {
        if (empty($this->buffer)) {
            return;
        }

        try {
            $written = file_put_contents('php://stderr', $this->buffer);

            if ($written === false) {
                error_log('Failed to write performance metrics to stderr');
            }
        } catch (\Throwable $e) {
            error_log("Error writing performance metrics: {$e->getMessage()}");
        } finally {
            $this->buffer = '';
        }
    }

    private function colorize(string $text, string $color): string
    {
        return self::COLORS[$color].$text.self::COLORS['reset'];
    }

    private function logPerformanceMetrics(Request $request): void
    {
        $metrics = $this->gatherMetrics();

        // Request information
        $this->appendToBuffer(sprintf(
            "\n%s %s %s[%s]%s\n",
            $this->colorize($request->method(), 'green'),
            $this->colorize($request->getRequestUri(), 'green'),
            self::COLORS['gray'],
            date('Y-m-d H:i:s'),
            self::COLORS['reset']
        ));

        // Core metrics
        $this->appendToBuffer(sprintf(
            '➜ Time: %s%.2fms%s | Memory: %s%.2fMB%s (%.1f%%) | Peak: %s%.2fMB%s',
            self::COLORS['yellow'],
            data_get($metrics, 'time'),
            self::COLORS['reset'],
            self::COLORS['magenta'],
            data_get($metrics, 'memory'),
            self::COLORS['reset'],
            data_get($metrics, 'memoryPercentage'),
            self::COLORS['magenta'],
            data_get($metrics, 'peakMemory'),
            self::COLORS['reset']
        ));

        // Query metrics
        if (data_get($metrics, 'queries')) {
            $this->appendToBuffer(sprintf(
                ' | Queries: %s%d%s (%s%.2fms%s)',
                self::COLORS['cyan'],
                data_get($metrics, 'queries.count'),
                self::COLORS['reset'],
                self::COLORS['cyan'],
                data_get($metrics, 'queries.time'),
                self::COLORS['reset']
            ));
        }

        // CPU metrics
        if (data_get($metrics, 'cpu')) {
            $totalCpu = data_get($metrics, 'cpu.user') + data_get($metrics, 'cpu.system');

            $this->appendToBuffer(sprintf(
                ' | CPU: %s%.2fms%s',
                self::COLORS['cyan'],
                $totalCpu,
                self::COLORS['reset']
            ));
        }

        // Cache metrics
        if (data_get($metrics, 'cache')) {
            $this->appendToBuffer(sprintf(
                ' | OPcache: %s%.1f%%%s',
                self::COLORS['cyan'],
                data_get($metrics, 'cache.hit_rate'),
                self::COLORS['reset']
            ));
        }

        $this->appendToBuffer("\n");

        // Log slow queries
        if (data_get($metrics, 'time') > self::SLOW_REQUEST_THRESHOLD && ! empty(data_get($metrics, 'queries.slow_queries'))) {
            $this->appendToBuffer("\n".$this->colorize('Slow Queries Detected:', 'yellow')."\n");

            foreach (data_get($metrics, 'queries.slow_queries') as $query) {
                $this->appendToBuffer(sprintf(
                    "➜ %s%.2fms%s: %s\n",
                    self::COLORS['yellow'],
                    data_get($query, 'time'),
                    self::COLORS['reset'],
                    data_get($query, 'sql')
                ));
            }
        }
    }
}
