<?php

namespace App\Services\Admin\Developer;

use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\LengthAwarePaginator as Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;

class LogViewerService
{
    private const ENTRY_PATTERN = '/^\[(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2})?)\]\s+(\w+)\.(\w+):\s?(.*)$/s';

    /**
     * @return Collection<int, array{name: string, size: int, modified_at: string}>
     */
    public function availableFiles(): Collection
    {
        return collect(File::glob(storage_path('logs/laravel*.log')))
            ->map(fn (string $path) => [
                'name' => basename($path),
                'size' => File::size($path),
                'modified_at' => date('Y-m-d H:i:s', File::lastModified($path)),
                'modified_timestamp' => File::lastModified($path),
            ])
            ->sortByDesc('modified_timestamp')
            ->values()
            ->map(fn (array $file) => collect($file)->except('modified_timestamp')->all());
    }

    public function latestFileName(): ?string
    {
        return $this->availableFiles()->first()['name'] ?? null;
    }

    public function paginated(string $fileName, int $perPage = 25, string $search = '', ?string $level = null): LengthAwarePaginator
    {
        $entries = $this->parse($fileName)
            ->when($level, fn (Collection $q) => $q->where('level', strtoupper($level)))
            ->when($search, fn (Collection $q) => $q->filter(
                fn (array $entry) => str_contains(strtolower($entry['message']), strtolower($search))
            ))
            ->values();

        $page = request()->integer('page', 1);
        $items = $entries->forPage($page, $perPage)->values();

        return new Paginator(
            $items,
            $entries->count(),
            $perPage,
            $page,
            ['path' => request()->url(), 'query' => request()->query()],
        );
    }

    /**
     * @return Collection<int, array{id: string, timestamp: string, environment: string, level: string, message: string, raw: string}>
     */
    private function parse(string $fileName): Collection
    {
        $path = $this->resolvePath($fileName);

        if (! $path) {
            return collect();
        }

        $lines = preg_split('/\R/', File::get($path)) ?: [];

        $entries = [];
        $current = null;

        foreach ($lines as $line) {
            if (preg_match(self::ENTRY_PATTERN, $line, $matches)) {
                if ($current) {
                    $entries[] = $current;
                }

                $current = [
                    'timestamp' => $matches[1],
                    'environment' => $matches[2],
                    'level' => strtoupper($matches[3]),
                    'message' => $matches[4],
                    'raw' => $line,
                ];
            } elseif ($current !== null && trim($line) !== '') {
                $current['message'] .= "\n".$line;
                $current['raw'] .= "\n".$line;
            }
        }

        if ($current) {
            $entries[] = $current;
        }

        return collect($entries)
            ->reverse()
            ->values()
            ->map(fn (array $entry, int $index) => [...$entry, 'id' => (string) $index]);
    }

    private function resolvePath(string $fileName): ?string
    {
        $allowed = $this->availableFiles()->pluck('name');

        if (! $allowed->contains($fileName)) {
            return null;
        }

        return storage_path('logs/'.$fileName);
    }
}
