<?php
declare(strict_types=1);

namespace SafeTun\Backend\Storage;

final class AuditLogger
{
    private const SAFE_FIELDS = [
        'event', 'outcome', 'request_id', 'ip_hash', 'node_key_hash', 'pair_id', 'error_code', 'route',
    ];

    public function __construct(private readonly string $directory)
    {
        SecureFiles::ensureDirectory($directory);
    }

    /** @param array<string,mixed> $fields */
    public function write(array $fields): void
    {
        $record = ['ts' => gmdate('Y-m-d\TH:i:s\Z')];
        foreach (self::SAFE_FIELDS as $field) {
            if (!array_key_exists($field, $fields)) {
                continue;
            }
            $value = $fields[$field];
            if (!is_scalar($value) && $value !== null) {
                continue;
            }
            $record[$field] = is_string($value) ? substr($value, 0, 256) : $value;
        }

        $path = rtrim($this->directory, '/\\') . DIRECTORY_SEPARATOR . gmdate('Y-m-d') . '.jsonl';
        $handle = @fopen($path, 'ab');
        if (!is_resource($handle)) {
            throw new \RuntimeException('Unable to open private audit log.');
        }
        try {
            if (!flock($handle, LOCK_EX)) {
                throw new \RuntimeException('Unable to lock private audit log.');
            }
            SecureFiles::writeAll($handle, SecureFiles::encodeJson($record));
            @chmod($path, 0600);
            flock($handle, LOCK_UN);
        } finally {
            fclose($handle);
        }
    }

    public function cleanup(int $olderThan, int $maximumFiles = 500): int
    {
        $removed = 0;
        $seen = 0;
        $iterator = new \DirectoryIterator($this->directory);
        foreach ($iterator as $file) {
            if ($file->isDot() || !$file->isFile() || $file->isLink()
                || !preg_match('/^\d{4}-\d{2}-\d{2}\.jsonl$/D', $file->getFilename())) {
                continue;
            }
            if (++$seen > $maximumFiles) {
                break;
            }
            if ($file->getMTime() >= $olderThan) {
                continue;
            }
            $handle = @fopen($file->getPathname(), 'r+b');
            if (!is_resource($handle)) {
                continue;
            }
            if (flock($handle, LOCK_EX | LOCK_NB)) {
                if (@unlink($file->getPathname())) {
                    $removed++;
                }
                flock($handle, LOCK_UN);
            }
            fclose($handle);
        }
        return $removed;
    }
}
