<?php

declare(strict_types=1);

namespace ImovelZapAi\Publishing\Connectors\GroupOlx\Infrastructure\Storage;

use DateTimeImmutable;
use DateTimeZone;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
use ImovelZapAi\Publishing\Connectors\GroupOlx\Application\Data\FeedStorageVersionData;
use ImovelZapAi\Publishing\Connectors\GroupOlx\Application\Ports\FeedStorageInterface;
use ImovelZapAi\Publishing\Connectors\GroupOlx\Domain\Enums\FeedStorageDriver;

/**
 * FeedStorage baseado em disco Laravel (Local ou Cloudflare R2 via S3).
 *
 * Estrutura:
 *   {base}/current.xml
 *   {base}/meta.json
 *   {base}/versions/{version}_{hash8}.xml
 */
final class FeedStorage implements FeedStorageInterface
{
    public function __construct(
        private readonly FeedStorageDriver $driver,
        private readonly string $disk,
        private readonly string $pathPrefixTemplate,
        private readonly int $keepVersions = 10,
    ) {}

    public static function fromConfig(): self
    {
        $driver = FeedStorageDriver::tryFrom((string) config('publishing.group_olx.storage.driver', 'local'))
            ?? FeedStorageDriver::Local;

        $disk = match ($driver) {
            FeedStorageDriver::Local => (string) config(
                'publishing.group_olx.storage.local_disk',
                config('publishing.group_olx.feed_cache.disk', 'local'),
            ),
            FeedStorageDriver::R2 => (string) config('publishing.group_olx.storage.r2_disk', 'r2'),
        };

        $prefix = (string) config(
            'publishing.group_olx.storage.path_prefix',
            'publishing/{tenant_id}/group-olx',
        );

        $keep = (int) config('publishing.group_olx.storage.keep_versions', 10);

        return new self($driver, $disk, $prefix, max(1, $keep));
    }

    public function store(string $tenantId, string $xml): FeedStorageVersionData
    {
        $hash = hash('sha256', $xml);
        $storedAt = new DateTimeImmutable('now', new DateTimeZone('UTC'));
        $version = $storedAt->format('Ymd\THis\Z');
        $base = $this->basePath($tenantId);
        $versionPath = $base.'/versions/'.$version.'_'.substr($hash, 0, 8).'.xml';
        $currentPath = $base.'/current.xml';
        $metaPath = $base.'/meta.json';

        $filesystem = $this->filesystem();
        $filesystem->put($versionPath, $xml);
        $filesystem->put($currentPath, $xml);
        $filesystem->put($metaPath, json_encode([
            'tenant_id' => $tenantId,
            'version' => $version,
            'hash' => $hash,
            'stored_at' => $storedAt->format(DATE_ATOM),
            'path' => $versionPath,
            'current_path' => $currentPath,
            'driver' => $this->driver->value,
        ], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));

        $this->pruneOldVersions($filesystem, $base, $versionPath);

        return new FeedStorageVersionData(
            tenantId: $tenantId,
            version: $version,
            hash: $hash,
            storedAt: $storedAt,
            path: $versionPath,
            currentPath: $currentPath,
            driver: $this->driver,
            xml: $xml,
        );
    }

    public function latest(string $tenantId, bool $withXml = true): ?FeedStorageVersionData
    {
        $metaPath = $this->basePath($tenantId).'/meta.json';
        $filesystem = $this->filesystem();

        if (! $filesystem->exists($metaPath)) {
            // Compatibilidade com path legado (Etapa 5).
            return $this->latestFromLegacy($tenantId, $withXml);
        }

        $raw = $filesystem->get($metaPath);

        if (! is_string($raw) || $raw === '') {
            return null;
        }

        /** @var array{tenant_id?: string, version?: string, hash?: string, stored_at?: string, path?: string, current_path?: string, driver?: string} $meta */
        $meta = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);

        if (! isset($meta['version'], $meta['hash'], $meta['stored_at'], $meta['path'], $meta['current_path'])) {
            return null;
        }

        $xml = null;
        if ($withXml) {
            $contents = $filesystem->get($meta['current_path']);
            $xml = is_string($contents) && $contents !== '' ? $contents : null;

            if ($xml === null) {
                return null;
            }
        }

        return new FeedStorageVersionData(
            tenantId: $tenantId,
            version: $meta['version'],
            hash: $meta['hash'],
            storedAt: new DateTimeImmutable($meta['stored_at']),
            path: $meta['path'],
            currentPath: $meta['current_path'],
            driver: FeedStorageDriver::tryFrom((string) ($meta['driver'] ?? $this->driver->value)) ?? $this->driver,
            xml: $xml,
        );
    }

    public function get(string $tenantId): ?string
    {
        return $this->latest($tenantId, withXml: true)?->xml;
    }

    public function exists(string $tenantId): bool
    {
        $filesystem = $this->filesystem();
        $base = $this->basePath($tenantId);

        return $filesystem->exists($base.'/current.xml')
            || $filesystem->exists($this->legacyPath($tenantId));
    }

    public function delete(string $tenantId): void
    {
        $filesystem = $this->filesystem();
        $base = $this->basePath($tenantId);

        if ($filesystem->exists($base.'/meta.json') || $filesystem->exists($base.'/current.xml')) {
            $filesystem->deleteDirectory($base);
        }

        $legacy = $this->legacyPath($tenantId);
        if ($filesystem->exists($legacy)) {
            $filesystem->delete($legacy);
        }
    }

    public function lastModified(string $tenantId): ?int
    {
        $filesystem = $this->filesystem();
        $current = $this->basePath($tenantId).'/current.xml';

        if ($filesystem->exists($current)) {
            return $filesystem->lastModified($current);
        }

        $legacy = $this->legacyPath($tenantId);

        return $filesystem->exists($legacy) ? $filesystem->lastModified($legacy) : null;
    }

    public function driver(): FeedStorageDriver
    {
        return $this->driver;
    }

    private function filesystem(): Filesystem
    {
        return Storage::disk($this->disk);
    }

    private function basePath(string $tenantId): string
    {
        return str_replace('{tenant_id}', $tenantId, $this->pathPrefixTemplate);
    }

    private function legacyPath(string $tenantId): string
    {
        $template = (string) config(
            'publishing.group_olx.feed_cache.path_template',
            'publishing/{tenant_id}/group-olx/vrsync.xml',
        );

        return str_replace('{tenant_id}', $tenantId, $template);
    }

    private function latestFromLegacy(string $tenantId, bool $withXml): ?FeedStorageVersionData
    {
        $path = $this->legacyPath($tenantId);
        $filesystem = $this->filesystem();

        if (! $filesystem->exists($path)) {
            return null;
        }

        $xml = $filesystem->get($path);

        if (! is_string($xml) || $xml === '') {
            return null;
        }

        $mtime = $filesystem->lastModified($path) ?: time();
        $storedAt = (new DateTimeImmutable('@'.$mtime))->setTimezone(new DateTimeZone('UTC'));

        return new FeedStorageVersionData(
            tenantId: $tenantId,
            version: $storedAt->format('Ymd\THis\Z'),
            hash: hash('sha256', $xml),
            storedAt: $storedAt,
            path: $path,
            currentPath: $path,
            driver: $this->driver,
            xml: $withXml ? $xml : null,
        );
    }

    private function pruneOldVersions(Filesystem $filesystem, string $base, string $keepPath): void
    {
        $versionsDir = $base.'/versions';
        $others = collect($filesystem->files($versionsDir))
            ->reject(static fn (string $file): bool => $file === $keepPath)
            ->sort()
            ->values()
            ->all();

        $allowedOthers = max(0, $this->keepVersions - 1);
        $excess = count($others) - $allowedOthers;

        if ($excess <= 0) {
            return;
        }

        foreach (array_slice($others, 0, $excess) as $file) {
            $filesystem->delete($file);
        }
    }
}
