<?php

namespace Bastion\Handlers;

use Openguru\OpenCartFramework\Config\Settings;
use Symfony\Component\HttpFoundation\JsonResponse;

class LogsHandler
{
    private Settings $settings;

    public function __construct(Settings $settings)
    {
        $this->settings = $settings;
    }

    public function getLogs(): JsonResponse
    {
        $parsedLogs = [];

        $logsPath = $this->findLastLogsFileInDir(
            $this->settings->get('logs.path')
        );

        if ($logsPath) {
            $lines = $this->readLastLogsRows($logsPath, 100);
            $parsedLogs = $this->parseLogLines($lines);
        }

        return new JsonResponse(['data' => $parsedLogs]);
    }

    private function parseLogLines(array $lines): array
    {
        $parsed = [];

        $pattern = '/^\[([^\]]+)\]\s+([^.]+)\.(\w+):\s+(.+)$/s';

        foreach ($lines as $line) {
            $line = trim($line);
            if (empty($line)) {
                continue;
            }

            if (preg_match($pattern, $line, $matches)) {
                $datetime = $matches[1] ?? '';
                $channel = $matches[2] ?? '';
                $level = $matches[3] ?? '';
                $rest = $matches[4] ?? '';

                // Извлекаем сообщение и контекст
                // Контекст начинается с { и заканчивается соответствующим }
                $message = $rest;
                $context = null;

                // Ищем JSON контекст (начинается с {, может быть после пробела или сразу)
                $jsonStart = strpos($rest, ' {');
                if ($jsonStart === false) {
                    $jsonStart = strpos($rest, '{');
                } else {
                    $jsonStart++; // Пропускаем пробел перед {
                }

                if ($jsonStart !== false) {
                    $message = trim(substr($rest, 0, $jsonStart));
                    $jsonPart = substr($rest, $jsonStart);

                    // Находим конец JSON объекта, учитывая вложенность
                    $jsonEnd = $this->findJsonEnd($jsonPart);
                    if ($jsonEnd !== false) {
                        $contextJson = substr($jsonPart, 0, $jsonEnd + 1);
                        $decoded = json_decode($contextJson, true);
                        if (json_last_error() === JSON_ERROR_NONE) {
                            $context = $decoded;
                        }
                    }
                }

                // Форматируем дату для отображения (убираем микросекунды и временную зону для читаемости)
                $formattedDatetime = $this->formatDateTime($datetime);

                $message = rtrim($message, ' [] []');

                $parsed[] = [
                    'datetime' => $formattedDatetime,
                    'datetime_raw' => $datetime,
                    'channel' => $channel,
                    'level' => $level,
                    'message' => $message,
                    'context' => $context,
                    'raw' => $line,
                ];
            } else {
                // Если строка не соответствует формату, сохраняем как есть
                $parsed[] = [
                    'datetime' => '',
                    'datetime_raw' => '',
                    'channel' => '',
                    'level' => '',
                    'message' => $line,
                    'context' => null,
                    'raw' => $line,
                ];
            }
        }

        return $parsed;
    }

    /**
     * Находит позицию конца JSON объекта, учитывая вложенность
     * @param string $json JSON строка, начинающаяся с {
     * @return int|false Позиция закрывающей скобки или false, если не найдено
     */
    private function findJsonEnd(string $json)
    {
        $depth = 0;
        $inString = false;
        $escape = false;
        $len = strlen($json);

        for ($i = 0; $i < $len; $i++) {
            $char = $json[$i];

            if ($escape) {
                $escape = false;
                continue;
            }

            if ($char === '\\') {
                $escape = true;
                continue;
            }

            if ($char === '"') {
                $inString = !$inString;
                continue;
            }

            if ($inString) {
                continue;
            }

            if ($char === '{') {
                $depth++;
            } elseif ($char === '}') {
                $depth--;
                if ($depth === 0) {
                    return $i;
                }
            }
        }

        return false;
    }

    private function formatDateTime(string $datetime): string
    {
        // Парсим ISO 8601 формат: 2025-11-23T14:28:21.772518+00:00
        // Преобразуем в более читаемый формат: 2025-11-23 14:28:21
        if (preg_match('/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})/', $datetime, $dateMatches)) {
            return $dateMatches[1] . ' ' . $dateMatches[2];
        }

        return $datetime;
    }

    private function readLastLogsRows(string $path, int $lines = 1000, int $buffer = 4096): array
    {
        $f = fopen($path, 'rb');
        if (! $f) {
            return [];
        }

        $lineCount = 0;
        $chunk = '';

        fseek($f, 0, SEEK_END);
        $filesize = ftell($f);

        while ($filesize > 0 && $lineCount < $lines) {
            $seek = max($filesize - $buffer, 0);
            $readLength = $filesize - $seek;

            fseek($f, $seek);
            $chunk = fread($f, $readLength) . $chunk;

            $filesize = $seek;
            $lineCount = substr_count($chunk, "\n");
        }

        fclose($f);

        $linesArray = explode("\n", $chunk);

        return array_slice($linesArray, -$lines);
    }

    private function findLastLogsFileInDir(string $dir): ?string
    {
        $files = glob($dir . '/telecart-*.log');

        return $files ? end($files) : null;
    }
}
