<?php

declare(strict_types=1);

namespace App\Commerce;

use App\Commerce\Dto\CommerceReferenceContext;

final readonly class CommerceReferenceStore
{
    private string $directory;

    public function __construct(string $projectDir)
    {
        $this->directory = rtrim($projectDir, '/') . '/var/agent-commerce-context';

        if (!is_dir($this->directory) && !mkdir($this->directory, 0775, true) && !is_dir($this->directory)) {
            throw new \RuntimeException(sprintf(
                'Failed to create commerce reference directory: %s',
                $this->directory
            ));
        }
    }

    public function load(string $userId): ?CommerceReferenceContext
    {
        $path = $this->getPath($userId);

        if (!is_file($path)) {
            return null;
        }

        $content = file_get_contents($path);

        if ($content === false || trim($content) === '') {
            return null;
        }

        $decoded = json_decode($content, true);

        if (!is_array($decoded)) {
            return null;
        }

        return CommerceReferenceContext::fromArray($decoded);
    }

    public function save(string $userId, CommerceReferenceContext $context): void
    {
        $path = $this->getPath($userId);
        $tmpPath = $path . '.tmp';

        $json = json_encode(
            $context->toArray(),
            JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE
        );

        if (!is_string($json)) {
            throw new \RuntimeException('Failed to encode commerce reference context.');
        }

        if (file_put_contents($tmpPath, $json, LOCK_EX) === false) {
            throw new \RuntimeException(sprintf(
                'Failed to write commerce reference context: %s',
                $tmpPath
            ));
        }

        if (!rename($tmpPath, $path)) {
            @unlink($tmpPath);

            throw new \RuntimeException(sprintf(
                'Failed to move commerce reference context into place: %s',
                $path
            ));
        }
    }

    public function clear(string $userId): void
    {
        $path = $this->getPath($userId);

        if (is_file($path)) {
            @unlink($path);
        }
    }

    private function getPath(string $userId): string
    {
        $safeUserId = preg_replace('/[^a-zA-Z0-9_-]/', '_', trim($userId));
        $safeUserId = is_string($safeUserId) ? trim($safeUserId, '_') : '';

        if ($safeUserId === '') {
            throw new \InvalidArgumentException('User id must not be empty.');
        }

        return $this->directory . '/' . $safeUserId . '.json';
    }
}