<?php

declare(strict_types=1);

namespace App\Services\License;

use RuntimeException;

final class OfflineLicenseConsole
{
    public function __construct(private array $config = [])
    {
    }

    public function isEnabled(): bool
    {
        return $this->loadPrivateKey() !== null && function_exists('sodium_crypto_sign_detached');
    }

    public function privateKeyPath(): string
    {
        return APP_BASE_PATH . '/storage/license/private.key';
    }

    public function publicKeyPath(): string
    {
        return APP_BASE_PATH . '/storage/license/public.key';
    }

    public function issuedDir(): string
    {
        return APP_BASE_PATH . '/storage/license/issued';
    }

    public function registryPath(): string
    {
        return APP_BASE_PATH . '/storage/license/registry/licenses.json';
    }

    public function loadRegistry(): array
    {
        $path = $this->registryPath();
        if (!is_file($path)) {
            return [];
        }
        $raw = @file_get_contents($path);
        $decoded = json_decode((string) $raw, true);
        return is_array($decoded) ? $decoded : [];
    }

    public function issue(array $input): array
    {
        $privateKey = $this->loadPrivateKey();
        if ($privateKey === null) {
            throw new RuntimeException('المفتاح الخاص غير موجود أو غير صالح في storage/license/private.key');
        }

        $action = strtolower(trim((string) ($input['action'] ?? 'issue')));
        if (!in_array($action, ['issue', 'renew', 'reissue', 'revoke'], true)) {
            $action = 'issue';
        }

        $licenseId = trim((string) ($input['license_id'] ?? ''));
        if ($licenseId === '') {
            $licenseId = 'LIC-' . date('Ymd-His') . '-' . strtoupper(substr(bin2hex(random_bytes(3)), 0, 6));
        }

        $fingerprint = trim((string) ($input['fingerprint'] ?? ''));
        if ($fingerprint === '') {
            throw new RuntimeException('Fingerprint مطلوب.');
        }

        $status = strtolower(trim((string) ($input['status'] ?? 'active')));
        if ($action === 'revoke') {
            $status = 'revoked';
        }
        if (!in_array($status, ['active', 'trial', 'revoked', 'expired'], true)) {
            throw new RuntimeException('حالة الترخيص غير مدعومة.');
        }

        $issuedAt = trim((string) ($input['issued_at'] ?? date(DATE_ATOM)));
        $expiresAt = trim((string) ($input['expires_at'] ?? date(DATE_ATOM, strtotime('+1 year'))));
        if ($issuedAt === '' || strtotime($issuedAt) === false) {
            $issuedAt = date(DATE_ATOM);
        }
        if ($expiresAt !== '' && strtotime($expiresAt) === false) {
            throw new RuntimeException('تاريخ الانتهاء غير صالح.');
        }

        $registry = $this->loadRegistry();
        $current = $registry[$licenseId] ?? [];
        $revision = (int) ($current['revision'] ?? 0) + 1;

        $features = $this->normalizeFeatures($input['features'] ?? []);
        $license = [
            'type' => 'offline_license',
            'license_id' => $licenseId,
            'customer' => trim((string) ($input['customer'] ?? '')),
            'product' => trim((string) ($input['product'] ?? 'Store ERP')),
            'issued_at' => $issuedAt,
            'expires_at' => $expiresAt,
            'fingerprint' => $fingerprint,
            'status' => $status,
            'features' => $features,
            'revision' => $revision,
            'reason' => trim((string) ($input['reason'] ?? '')),
            'notes' => trim((string) ($input['notes'] ?? '')),
        ];

        $manager = new LicenseManager($this->config);
        $payload = $manager->canonicalPayload($license);
        $license['signature'] = base64_encode(sodium_crypto_sign_detached($payload, $privateKey));

        $this->ensureDirs();
        $fileName = $this->buildIssuedFileName($licenseId, $fingerprint, $revision, $status);
        $filePath = $this->issuedDir() . '/' . $fileName;
        file_put_contents($filePath, json_encode($license, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL, LOCK_EX);

        $registry[$licenseId] = [
            'license_id' => $licenseId,
            'customer' => $license['customer'],
            'product' => $license['product'],
            'fingerprint' => $license['fingerprint'],
            'status' => $license['status'],
            'expires_at' => $license['expires_at'],
            'issued_at' => $license['issued_at'],
            'revision' => $revision,
            'last_action' => $action,
            'reason' => $license['reason'],
            'notes' => $license['notes'],
            'features' => $license['features'],
            'file' => $fileName,
            'updated_at' => date(DATE_ATOM),
        ];
        $this->saveRegistry($registry);

        return [
            'license' => $license,
            'file_name' => $fileName,
            'file_path' => $filePath,
            'registry' => $registry[$licenseId],
        ];
    }

    public function generateRequestDocument(): array
    {
        return [
            'type' => 'offline_activation_request',
            'product' => 'Store ERP',
            'app_name' => (string) ($this->config['app']['name'] ?? 'Store ERP'),
            'generated_at' => date(DATE_ATOM),
            'fingerprint' => ServerFingerprint::value($this->config),
            'fingerprint_data' => ServerFingerprint::collect($this->config),
            'license_path' => APP_BASE_PATH . '/storage/license/license.json',
            'public_key_path' => APP_BASE_PATH . '/storage/license/public.key',
        ];
    }

    public function exportRequestFile(): array
    {
        $this->ensureDirs();
        $request = $this->generateRequestDocument();
        $name = 'license-request-' . substr($request['fingerprint'], 0, 12) . '.json';
        $path = APP_BASE_PATH . '/storage/license/' . $name;
        file_put_contents($path, json_encode($request, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL, LOCK_EX);
        return ['file_name' => $name, 'file_path' => $path, 'payload' => $request];
    }

    public function generateKeypair(bool $overwrite = false): array
    {
        if (!$overwrite && is_file($this->privateKeyPath()) && is_file($this->publicKeyPath())) {
            return ['created' => false, 'private_key_path' => $this->privateKeyPath(), 'public_key_path' => $this->publicKeyPath()];
        }

        if (!function_exists('sodium_crypto_sign_keypair')) {
            throw new RuntimeException('libsodium غير متوفر على هذا السيرفر.');
        }

        $this->ensureDirs();
        $pair = sodium_crypto_sign_keypair();
        $secret = sodium_crypto_sign_secretkey($pair);
        $public = sodium_crypto_sign_publickey($pair);

        file_put_contents($this->privateKeyPath(), base64_encode($secret) . PHP_EOL, LOCK_EX);
        file_put_contents($this->publicKeyPath(), base64_encode($public) . PHP_EOL, LOCK_EX);

        return ['created' => true, 'private_key_path' => $this->privateKeyPath(), 'public_key_path' => $this->publicKeyPath()];
    }

    public function readIssuedFile(string $fileName): array
    {
        $safe = basename($fileName);
        $path = $this->issuedDir() . '/' . $safe;
        if (!is_file($path)) {
            throw new RuntimeException('ملف الترخيص المطلوب غير موجود.');
        }
        return ['path' => $path, 'name' => $safe];
    }

    private function normalizeFeatures(mixed $features): array
    {
        if (is_string($features)) {
            $decoded = json_decode($features, true);
            $features = is_array($decoded) ? $decoded : [];
        }
        if (!is_array($features)) {
            return [
                'sales' => true,
                'inventory' => true,
                'reports' => true,
                'pos' => true,
            ];
        }
        $normalized = [];
        foreach ($features as $key => $value) {
            $normalized[(string) $key] = filter_var($value, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE);
            if ($normalized[(string) $key] === null) {
                $normalized[(string) $key] = (bool) $value;
            }
        }
        return $normalized;
    }

    private function buildIssuedFileName(string $licenseId, string $fingerprint, int $revision, string $status): string
    {
        $id = preg_replace('/[^A-Za-z0-9\-_.]+/', '-', $licenseId) ?: 'license';
        return sprintf('%s-r%d-%s-%s.json', $id, $revision, substr($fingerprint, 0, 12), $status);
    }

    private function ensureDirs(): void
    {
        @mkdir(dirname($this->registryPath()), 0755, true);
        @mkdir($this->issuedDir(), 0755, true);
        @mkdir(APP_BASE_PATH . '/storage/license', 0755, true);
    }

    private function saveRegistry(array $registry): void
    {
        $this->ensureDirs();
        file_put_contents($this->registryPath(), json_encode($registry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL, LOCK_EX);
    }

    private function loadPrivateKey(): ?string
    {
        $path = $this->privateKeyPath();
        if (!is_file($path)) {
            return null;
        }
        $raw = trim((string) @file_get_contents($path));
        if ($raw === '' || str_contains($raw, 'PUT_BASE64_')) {
            return null;
        }
        $decoded = base64_decode($raw, true);
        return is_string($decoded) && $decoded !== '' ? $decoded : null;
    }
}
