<?php

declare(strict_types=1);

namespace App\Services\Install;

use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\File;
use InvalidArgumentException;
use JsonException;

/**
 * 暂存待执行的 Web 安装表单数据（storage/framework/dberp-install/payloads/{install_id}.json）。
 *
 * 管理员口令以加密形式落盘，禁止写入日志。
 */
final class WebInstallPendingPayloadStore
{
    private const PASSWORD_KEY = 'password';

    public function __construct(
        private readonly ?string $payloadDirectory = null,
    ) {}

    /**
     * @param  array{
     *     system_url: string,
     *     timezone: string,
     *     admin_name: string,
     *     login_account: string,
     *     admin_email?: string|null,
     *     password: string,
     *     reinstall_reset?: bool,
     * }  $payload
     */
    public function store(string $installId, array $payload): void
    {
        $this->assertValidInstallId($installId);

        $record = [
            'system_url' => (string) $payload['system_url'],
            'timezone' => (string) $payload['timezone'],
            'admin_name' => (string) $payload['admin_name'],
            'login_account' => (string) $payload['login_account'],
            'admin_email' => $payload['admin_email'] ?? null,
            self::PASSWORD_KEY => Crypt::encryptString((string) $payload['password']),
            'reinstall_reset' => (bool) ($payload['reinstall_reset'] ?? false),
        ];

        $this->writeRecord($installId, $record);
    }

    /**
     * @return array{
     *     system_url: string,
     *     timezone: string,
     *     admin_name: string,
     *     login_account: string,
     *     admin_email?: string|null,
     *     password: string,
     *     reinstall_reset: bool,
     * }|null
     */
    public function readForExecution(string $installId): ?array
    {
        $this->assertValidInstallId($installId);

        $record = $this->readRecord($installId);
        if ($record === null) {
            return null;
        }

        $encryptedPassword = (string) ($record[self::PASSWORD_KEY] ?? '');
        if ($encryptedPassword === '') {
            return null;
        }

        return [
            'system_url' => (string) ($record['system_url'] ?? ''),
            'timezone' => (string) ($record['timezone'] ?? ''),
            'admin_name' => (string) ($record['admin_name'] ?? ''),
            'login_account' => (string) ($record['login_account'] ?? ''),
            'admin_email' => $record['admin_email'] ?? null,
            'password' => Crypt::decryptString($encryptedPassword),
            'reinstall_reset' => (bool) ($record['reinstall_reset'] ?? false),
        ];
    }

    public function exists(string $installId): bool
    {
        $this->assertValidInstallId($installId);

        return is_file($this->pathFor($installId));
    }

    public function delete(string $installId): bool
    {
        $this->assertValidInstallId($installId);

        $path = $this->pathFor($installId);
        if (! is_file($path)) {
            return false;
        }

        return unlink($path);
    }

    /**
     * @param  array<string, mixed>  $record
     */
    private function writeRecord(string $installId, array $record): void
    {
        $path = $this->pathFor($installId);
        File::ensureDirectoryExists(dirname($path));

        try {
            $payload = json_encode(
                $record,
                JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
            );
        } catch (JsonException $e) {
            throw new InvalidArgumentException('无法序列化安装暂存数据。', 0, $e);
        }

        $payload .= "\n";
        $temporaryPath = $path.'.tmp.'.uniqid('', true);

        if (file_put_contents($temporaryPath, $payload, LOCK_EX) === false) {
            throw new InvalidArgumentException('无法写入安装暂存临时文件。');
        }

        @chmod($temporaryPath, 0600);

        if (is_file($path) && ! @unlink($path)) {
            @unlink($temporaryPath);

            throw new InvalidArgumentException('无法替换已有安装暂存文件。');
        }

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

            throw new InvalidArgumentException('无法落盘安装暂存文件。');
        }

        @chmod($path, 0600);
    }

    /**
     * @return array<string, mixed>|null
     */
    private function readRecord(string $installId): ?array
    {
        $path = $this->pathFor($installId);
        if (! is_file($path)) {
            return null;
        }

        $raw = file_get_contents($path);
        if ($raw === false || trim($raw) === '') {
            return null;
        }

        try {
            /** @var array<string, mixed> $decoded */
            $decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            return null;
        }

        return $decoded;
    }

    private function pathFor(string $installId): string
    {
        return $this->directory().DIRECTORY_SEPARATOR.$installId.'.json';
    }

    private function directory(): string
    {
        return $this->payloadDirectory ?? storage_path('framework/dberp-install/payloads');
    }

    private function assertValidInstallId(string $installId): void
    {
        if (! preg_match(
            '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i',
            $installId,
        )) {
            throw new InvalidArgumentException('install_id 格式无效。');
        }
    }
}
