<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Services\AuditService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\View\View;
use RuntimeException;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

class LaravelLogController extends Controller
{
    private const LINE_OPTIONS = [100, 250, 500, 1000, 2000];

    public function index(Request $request): View
    {
        $logs = $this->logs();
        $requestedLines = (int) $request->query('lines', 500);
        $lines = in_array($requestedLines, self::LINE_OPTIONS, true) ? $requestedLines : 500;
        $requestedFile = (string) $request->query('file', '');

        $selected = $requestedFile !== '' ? $logs->firstWhere('filename', $requestedFile) : null;
        $selected ??= $logs->firstWhere('filename', 'laravel.log');
        $selected ??= $logs->first();

        $viewer = null;
        if ($selected) {
            $path = $this->resolve($selected['filename']);
            $viewer = $this->tail($path, $lines);
        }

        return view('admin.logs.index', [
            'logs' => $logs,
            'selected' => $selected,
            'viewer' => $viewer,
            'lines' => $lines,
            'lineOptions' => self::LINE_OPTIONS,
            'summary' => [
                'count' => $logs->count(),
                'total_size' => (int) $logs->sum('size'),
                'latest' => $logs->sortByDesc('modified_at')->first(),
                'directory_writable' => is_writable($this->directory()),
            ],
        ]);
    }

    public function download(string $filename): BinaryFileResponse|RedirectResponse
    {
        try {
            $path = $this->resolve($filename);

            AuditService::record('system.log.downloaded', null, [], [
                'filename' => basename($path),
                'size' => (int) (filesize($path) ?: 0),
            ]);

            return response()->download($path, basename($path), [
                'Content-Type' => 'text/plain; charset=UTF-8',
                'Cache-Control' => 'private, no-store, max-age=0',
            ]);
        } catch (RuntimeException $exception) {
            return back()->with('error', $exception->getMessage());
        }
    }

    public function clear(string $filename): RedirectResponse
    {
        try {
            $path = $this->resolve($filename);
            $previousSize = (int) (filesize($path) ?: 0);

            if (file_put_contents($path, '', LOCK_EX) === false) {
                throw new RuntimeException('The log file could not be cleared. Check storage/logs permissions.');
            }

            AuditService::record('system.log.cleared', null, [
                'filename' => basename($path),
                'size' => $previousSize,
            ], [
                'filename' => basename($path),
                'size' => 0,
            ]);

            return redirect()
                ->route('admin.laravel-logs.index', ['file' => basename($path)])
                ->with('success', 'Log file cleared successfully.');
        } catch (RuntimeException $exception) {
            return back()->with('error', $exception->getMessage());
        }
    }

    public function destroy(string $filename): RedirectResponse
    {
        if ($filename === 'laravel.log') {
            return back()->with('error', 'The primary laravel.log file cannot be deleted. Clear it instead.');
        }

        try {
            $path = $this->resolve($filename);
            $size = (int) (filesize($path) ?: 0);

            if (! File::delete($path)) {
                throw new RuntimeException('The log file could not be deleted. Check storage/logs permissions.');
            }

            AuditService::record('system.log.deleted', null, [
                'filename' => basename($path),
                'size' => $size,
            ], []);

            return redirect()
                ->route('admin.laravel-logs.index')
                ->with('success', 'Log file deleted successfully.');
        } catch (RuntimeException $exception) {
            return back()->with('error', $exception->getMessage());
        }
    }

    private function directory(): string
    {
        $directory = storage_path('logs');
        File::ensureDirectoryExists($directory, 0750, true);

        return $directory;
    }

    private function logs(): Collection
    {
        return collect(File::files($this->directory()))
            ->filter(fn ($file): bool => $file->isFile() && $this->isAllowedFilename($file->getFilename()))
            ->map(fn ($file): array => [
                'filename' => $file->getFilename(),
                'size' => (int) $file->getSize(),
                'modified_at' => (int) $file->getMTime(),
                'writable' => $file->isWritable(),
            ])
            ->sortByDesc('modified_at')
            ->values();
    }

    private function resolve(string $filename): string
    {
        if (! $this->isAllowedFilename($filename)) {
            throw new RuntimeException('Invalid log file name.');
        }

        $path = $this->directory().DIRECTORY_SEPARATOR.$filename;
        if (! is_file($path)) {
            throw new RuntimeException('The selected log file no longer exists.');
        }

        $realDirectory = realpath($this->directory());
        $realPath = realpath($path);
        if ($realDirectory === false || $realPath === false || ! str_starts_with($realPath, $realDirectory.DIRECTORY_SEPARATOR)) {
            throw new RuntimeException('Invalid log file path.');
        }

        return $realPath;
    }

    private function isAllowedFilename(string $filename): bool
    {
        return basename($filename) === $filename
            && preg_match('/^[A-Za-z0-9][A-Za-z0-9._-]*\.log$/', $filename) === 1;
    }

    private function tail(string $path, int $lines): array
    {
        $size = (int) (filesize($path) ?: 0);
        if ($size === 0) {
            return ['content' => '', 'truncated' => false];
        }

        $handle = fopen($path, 'rb');
        if ($handle === false) {
            throw new RuntimeException('The selected log file could not be opened.');
        }

        $maxBytes = 2 * 1024 * 1024;
        $chunkSize = 8192;
        $position = $size;
        $buffer = '';

        try {
            while ($position > 0 && substr_count($buffer, "\n") <= $lines && strlen($buffer) < $maxBytes) {
                $remainingCapacity = $maxBytes - strlen($buffer);
                $readSize = min($chunkSize, $position, $remainingCapacity);
                if ($readSize <= 0) {
                    break;
                }

                $position -= $readSize;
                fseek($handle, $position);
                $chunk = fread($handle, $readSize);
                if ($chunk === false) {
                    break;
                }

                $buffer = $chunk.$buffer;
            }
        } finally {
            fclose($handle);
        }

        $rows = preg_split('/\r\n|\r|\n/', $buffer) ?: [];
        if ($rows !== [] && end($rows) === '') {
            array_pop($rows);
        }

        $truncated = $position > 0 || count($rows) > $lines;
        $rows = array_slice($rows, -$lines);

        return [
            'content' => implode(PHP_EOL, $rows),
            'truncated' => $truncated,
        ];
    }
}
