<?php

namespace App\Controllers;

use CodeIgniter\API\ResponseTrait;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\ResponseInterface;

class LogViewerController extends BaseController
{
    use ResponseTrait;

    private string $logsPath;

    public function __construct()
    {
        $this->logsPath = rtrim(WRITEPATH . 'logs', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    }

    public function index(): string
    {
        return view('logs/index');
    }

    public function list(): ResponseInterface
    {
        $search = trim((string) ($this->request->getGet('q') ?? ''));
        $page   = max(1, (int) ($this->request->getGet('page') ?? 1));
        $limit  = max(1, min(50, (int) ($this->request->getGet('limit') ?? 10)));

        $all    = $this->listLogFiles($search !== '' ? $search : null);
        $total  = count($all);
        $offset = ($page - 1) * $limit;
        $files  = array_slice($all, $offset, $limit);
        $pages  = $total > 0 ? (int) ceil($total / $limit) : 0;

        return $this->respond([
            'status'     => 'success',
            'files'      => $files,
            'page'       => $page,
            'limit'      => $limit,
            'total'      => $total,
            'total_pages'=> $pages,
            'has_more'   => $page < $pages,
        ]);
    }

    public function view(): string|ResponseInterface
    {
        $file = $this->resolveLogFile((string) ($this->request->getGet('file') ?? ''));

        if ($file === null) {
            return $this->response->setStatusCode(404)->setBody('Log file not found.');
        }

        $content = @file_get_contents($file['path']);
        if ($content === false) {
            return $this->response->setStatusCode(500)->setBody('Unable to read log file.');
        }

        $maxBytes  = 2 * 1024 * 1024;
        $truncated = false;
        if (strlen($content) > $maxBytes) {
            $content   = substr($content, -$maxBytes);
            $truncated = true;
        }

        return view('logs/view', [
            'file'      => $file,
            'content'   => $content,
            'truncated' => $truncated,
            'maxBytes'  => $maxBytes,
        ]);
    }

    public function download(): DownloadResponse|ResponseInterface
    {
        $file = $this->resolveLogFile((string) ($this->request->getGet('file') ?? ''));

        if ($file === null) {
            return $this->response->setStatusCode(404)->setBody('Log file not found.');
        }

        return $this->response->download($file['path'], null)->setFileName($file['name']);
    }

    /**
     * @return list<array{name: string, size: int, size_human: string, modified: string, modified_ts: int}>
     */
    private function listLogFiles(?string $search = null): array
    {
        if (! is_dir($this->logsPath)) {
            return [];
        }

        $files  = glob($this->logsPath . 'log-*.log') ?: [];
        $result = [];
        $needle = $search !== null ? strtolower($search) : null;

        foreach ($files as $path) {
            if (! is_file($path)) {
                continue;
            }

            $name = basename($path);
            if ($needle !== null && ! str_contains(strtolower($name), $needle)) {
                continue;
            }

            $size  = (int) filesize($path);
            $mtime = (int) filemtime($path);

            $result[] = [
                'name'        => $name,
                'size'        => $size,
                'size_human'  => $this->formatBytes($size),
                'modified'    => date('Y-m-d H:i:s', $mtime),
                'modified_ts' => $mtime,
            ];
        }

        usort($result, static fn (array $a, array $b): int => $b['modified_ts'] <=> $a['modified_ts']);

        return $result;
    }

    /**
     * @return array{name: string, path: string, size: int, size_human: string, modified: string}|null
     */
    private function resolveLogFile(string $name): ?array
    {
        $name = basename(trim($name));

        if ($name === '' || ! preg_match('/^log-\d{4}-\d{2}-\d{2}\.log$/', $name)) {
            return null;
        }

        $path     = $this->logsPath . $name;
        $realPath = realpath($path);
        $realLogs = realpath($this->logsPath);

        if ($realPath === false || $realLogs === false || ! str_starts_with($realPath, $realLogs) || ! is_file($realPath)) {
            return null;
        }

        $size = (int) filesize($realPath);

        return [
            'name'       => $name,
            'path'       => $realPath,
            'size'       => $size,
            'size_human' => $this->formatBytes($size),
            'modified'   => date('Y-m-d H:i:s', (int) filemtime($realPath)),
        ];
    }

    private function formatBytes(int $bytes): string
    {
        if ($bytes < 1024) {
            return $bytes . ' B';
        }

        $units = ['KB', 'MB', 'GB'];
        $value = (float) $bytes;

        foreach ($units as $unit) {
            $value /= 1024;
            if ($value < 1024) {
                return round($value, 2) . ' ' . $unit;
            }
        }

        return round($value / 1024, 2) . ' TB';
    }
}
