<?php

declare(strict_types=1);

namespace App\Services;

use RuntimeException;

/**
 * Filesystem-only export registry.
 * Oracle writes: NONE. Frozen snapshot writes: NONE.
 */
final class DiarExportManifestService
{
    private const STORAGE_RELATIVE = 'storage/diar-reports/exports';

    public function directory(): string
    {
        $path = base_path(self::STORAGE_RELATIVE);
        if (!is_dir($path) && !mkdir($path, 0777, true) && !is_dir($path)) {
            throw new RuntimeException('DIAR_EXPORT_STORAGE_CREATE_FAILED');
        }
        return $path;
    }

    /** @param array<string,mixed> $report @return array<string,string> */
    public function allocate(array $report, string $type): array
    {
        $type = strtoupper(trim($type));
        if (!in_array($type, ['XLSX', 'PPTX'], true)) {
            throw new RuntimeException('DIAR_EXPORT_TYPE_INVALID');
        }
        $scope = preg_replace('/[^A-Z0-9_-]+/', '_', strtoupper((string)($report['scope_code'] ?? 'DIAR')));
        $month = preg_replace('/[^0-9-]+/', '', (string)($report['selected_month'] ?? date('Y-m')));
        $stamp = date('Ymd_His');
        $token = strtolower($type) . '_' . strtolower($scope ?: 'diar') . '_' . str_replace('-', '', $month) . '_' . $stamp . '_' . substr(bin2hex(random_bytes(5)), 0, 10);
        $extension = strtolower($type);
        $filename = 'DIAR_' . ($scope ?: 'REPORT') . '_' . str_replace('-', '', $month) . '_' . $stamp . '.' . $extension;
        $dir = $this->directory();
        return [
            'token' => $token,
            'filename' => $filename,
            'file_path' => $dir . DIRECTORY_SEPARATOR . $token . '.' . $extension,
            'manifest_path' => $dir . DIRECTORY_SEPARATOR . $token . '.json',
        ];
    }

    /** @param array<string,mixed> $report @param array<string,string> $allocation @param array<string,mixed> $extra @return array<string,mixed> */
    public function register(array $report, array $allocation, string $type, string $username, array $extra = []): array
    {
        $file = (string)($allocation['file_path'] ?? '');
        if (!is_file($file)) {
            throw new RuntimeException('DIAR_EXPORT_FILE_NOT_FOUND');
        }
        $manifest = [
            'release' => 'DIAR-6B.5-R2.1',
            'token' => (string)($allocation['token'] ?? ''),
            'filename' => (string)($allocation['filename'] ?? basename($file)),
            'type' => strtoupper($type),
            'scope_code' => (string)($report['scope_code'] ?? ''),
            'selected_month' => (string)($report['selected_month'] ?? ''),
            'month_status' => (string)($report['month_status'] ?? ''),
            'snapshot_refs' => array_values((array)($report['snapshot_refs'] ?? [])),
            'summary' => (array)($report['summary'] ?? []),
            'approved_workbook_sha256' => DiarReportCatalog::APPROVED_WORKBOOK_SHA256,
            'generated_by' => $username,
            'generated_at' => date('c'),
            'file_size' => (int)filesize($file),
            'file_sha256' => strtolower((string)hash_file('sha256', $file)),
            'read_only_source_contract' => true,
            'operational_mm_writes' => 'NONE',
            'frozen_table_writes' => 'NONE',
            'database_link' => 'NONE',
            'extra' => $extra,
        ];
        $json = json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if ($json === false || file_put_contents((string)$allocation['manifest_path'], $json) === false) {
            throw new RuntimeException('DIAR_EXPORT_MANIFEST_WRITE_FAILED');
        }
        return $manifest;
    }

    /** @return array<int,array<string,mixed>> */
    public function history(int $limit = 30): array
    {
        $dir = $this->directory();
        $files = glob($dir . DIRECTORY_SEPARATOR . '*.json') ?: [];
        usort($files, static function (string $a, string $b): int {
            return (int)@filemtime($b) <=> (int)@filemtime($a);
        });
        $rows = [];
        foreach (array_slice($files, 0, max(1, min(100, $limit))) as $path) {
            $row = json_decode((string)@file_get_contents($path), true);
            if (!is_array($row) || empty($row['token'])) {
                continue;
            }
            $rows[] = $row;
        }
        return $rows;
    }

    /** @return array<string,mixed> */
    public function find(string $token): array
    {
        if (preg_match('/^[a-z0-9_-]{10,160}$/', $token) !== 1) {
            throw new RuntimeException('DIAR_EXPORT_TOKEN_INVALID');
        }
        $path = $this->directory() . DIRECTORY_SEPARATOR . $token . '.json';
        $row = json_decode((string)@file_get_contents($path), true);
        if (!is_array($row)) {
            throw new RuntimeException('DIAR_EXPORT_MANIFEST_NOT_FOUND');
        }
        $extension = strtolower((string)($row['type'] ?? ''));
        $file = $this->directory() . DIRECTORY_SEPARATOR . $token . '.' . $extension;
        if (!is_file($file)) {
            throw new RuntimeException('DIAR_EXPORT_FILE_NOT_FOUND');
        }
        $row['file_path'] = $file;
        return $row;
    }
}
