<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\Artwork;
use App\Services\ArtworkOriginalFileLocator;
use App\Services\Uploads\UploadStorageService;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;

final class AuditArtworkDownloadFilesCommand extends Command
{
    protected $signature = 'artworks:audit-download-files
        {--id= : Audit only this artwork ID}
        {--limit= : Stop after processing this many artworks}
        {--chunk=500 : Number of artworks to scan per batch}
        {--restore-missing : Copy missing local originals from object storage when available}';

    protected $description = 'Scan artworks in descending ID order and report missing local download files with full URLs.';

    public function handle(ArtworkOriginalFileLocator $locator, UploadStorageService $storage): int
    {
        $artworkId = $this->option('id') !== null ? max(1, (int) $this->option('id')) : null;
        $limit = $this->option('limit') !== null ? max(1, (int) $this->option('limit')) : null;
        $chunkSize = max(1, min((int) $this->option('chunk'), 2000));
        $restoreMissing = (bool) $this->option('restore-missing');

        $this->info(sprintf(
            'Starting download file audit. order=desc include_trashed=yes chunk=%d limit=%s restore_missing=%s',
            $chunkSize,
            $limit !== null ? (string) $limit : 'all',
            $restoreMissing ? 'yes' : 'no',
        ));

        $processed = 0;
        $missing = 0;
        $unresolved = 0;
        $restored = 0;
        $restoreFailed = 0;
        $lastSeenId = null;

        do {
            $artworks = $this->nextChunk($artworkId, $chunkSize, $lastSeenId);
            if ($artworks->isEmpty()) {
                break;
            }

            foreach ($artworks as $artwork) {
                if ($limit !== null && $processed >= $limit) {
                    break 2;
                }

                $localPath = $locator->resolveLocalPath($artwork);
                $missingReason = null;

                if ($localPath === '') {
                    $missingReason = 'unresolved_local_path';
                    $unresolved++;
                } elseif (! File::isFile($localPath)) {
                    $missingReason = 'missing_local_file';
                }

                if ($missingReason !== null) {
                    $objectPath = $locator->resolveObjectPath($artwork);
                    $objectUrl = $locator->resolveObjectUrl($artwork);

                    $missing++;
                    $this->warn(sprintf('Artwork %d %s', (int) $artwork->id, $missingReason));
                    $this->line('  artwork_url: ' . route('art.show', [
                        'id' => (int) $artwork->id,
                        'slug' => (string) ($artwork->slug ?? ''),
                    ]));
                    $this->line('  download_url: ' . route('art.download', ['id' => (int) $artwork->id]));

                    if ($objectPath !== '') {
                        $this->line('  object_path: ' . $objectPath);
                    }

                    if ($objectUrl !== null && $objectUrl !== '') {
                        $this->line('  object_url: ' . $objectUrl);
                    }

                    if ($localPath !== '') {
                        $this->line('  local_path: ' . $localPath);
                    }

                    if ($restoreMissing && $missingReason === 'missing_local_file' && $localPath !== '') {
                        $restoreResult = $this->restoreLocalFile($storage, $objectPath, $localPath);

                        if ($restoreResult === 'restored') {
                            $restored++;
                            $this->info('  restore: restored from object storage');
                        } elseif ($restoreResult === 'object_missing') {
                            $restoreFailed++;
                            $this->warn('  restore: object storage file not found');
                        } else {
                            $restoreFailed++;
                            $this->warn('  restore: failed to copy object to local path');
                        }
                    }

                    $this->line('');
                }

                $processed++;
            }

            $lastSeenId = (int) $artworks->last()->id;
        } while (true);

        $this->info(sprintf(
            'Download file audit complete. processed=%d missing=%d unresolved=%d restored=%d restore_failed=%d',
            $processed,
            $missing,
            $unresolved,
            $restored,
            $restoreFailed,
        ));

        return self::SUCCESS;
    }

    /**
     * @return Collection<int, Artwork>
     */
    private function nextChunk(?int $artworkId, int $chunkSize, ?int $lastSeenId): Collection
    {
        $query = Artwork::query()
            ->withTrashed()
            ->select(['id', 'slug', 'file_path', 'hash', 'file_ext'])
            ->orderByDesc('id');

        if ($artworkId !== null) {
            $query->whereKey($artworkId);
        } elseif ($lastSeenId !== null) {
            $query->where('id', '<', $lastSeenId);
        }

        return $query->limit($chunkSize)->get();
    }

    private function restoreLocalFile(UploadStorageService $storage, string $objectPath, string $localPath): string
    {
        if ($objectPath === '') {
            return 'object_missing';
        }

        $disk = Storage::disk($storage->objectDiskName());
        if (! $disk->exists($objectPath)) {
            return 'object_missing';
        }

        $stream = $disk->readStream($objectPath);
        if (! is_resource($stream)) {
            return 'failed';
        }

        File::ensureDirectoryExists(dirname($localPath));

        $target = fopen($localPath, 'wb');
        if (! is_resource($target)) {
            fclose($stream);

            return 'failed';
        }

        try {
            $copied = stream_copy_to_stream($stream, $target);
        } finally {
            fclose($stream);
            fclose($target);
        }

        if ($copied === false || $copied <= 0 || ! File::isFile($localPath)) {
            return 'failed';
        }

        return 'restored';
    }
}
