<?php

declare(strict_types=1);


namespace App\Console\Commands;

use App\Support\MediaDisk;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class AttachmentsSyncS3 extends Command
{
    protected $signature = 'attachments:sync-s3
        {--legacy-dir= : 老 sablog attachments 目录（用于找回 pending 附件）}
        {--disk= : 目标磁盘（默认取配置的附件磁盘）}
        {--force : 重新上传已有文件（校验失败时）}';

    protected $description = '把遗留附件同步到 S3 兼容存储（R2/COS/OSS），大文件走 Multipart，幂等可断点续传';

    public function handle(): int
    {
        $disk = $this->option('disk') ?: MediaDisk::name();
        $s3 = Storage::disk($disk);

        $this->info("目标磁盘：{$disk}");

        $query = Media::query()->where('collection_name', 'attachments');

        if (! $this->option('force')) {
            $query->where(function ($q) use ($disk) {
                $q->whereRaw('JSON_EXTRACT(custom_properties, "$.pending_sync") = true')
                    ->orWhere('disk', '!=', $disk);
            });
        }

        $mediaItems = $query->get();

        if ($mediaItems->isEmpty()) {
            $this->info('没有需要同步的附件');

            return self::SUCCESS;
        }

        $bar = $this->output->createProgressBar($mediaItems->count());
        $bar->start();

        $synced = 0;
        $skipped = 0;
        $failed = 0;

        foreach ($mediaItems as $media) {
            $result = $this->syncOne($media, $s3, $disk);

            match ($result) {
                'synced' => $synced++,
                'skipped' => $skipped++,
                'failed' => $failed++,
            };

            $bar->advance();
        }

        $bar->finish();
        $this->newLine();
        $this->line("已同步：{$synced}，跳过：{$skipped}，失败：{$failed}");

        return $failed === 0 ? self::SUCCESS : self::FAILURE;
    }

    private function syncOne(Media $media, \Illuminate\Contracts\Filesystem\Filesystem $s3, string $disk): string
    {
        // 1. 找源文件：优先已存在磁盘上的文件，其次老 attachments 目录
        $source = $this->locateSource($media);

        if (! $source) {
            return 'skipped';
        }

        $targetPath = $this->targetPath($media, $disk);

        try {
            if ($s3->exists($targetPath) && ! $this->option('force')) {
                $this->markSynced($media, $disk);

                return 'synced';
            }

            $this->upload($s3, $targetPath, $source);

            $this->markSynced($media, $disk);

            return 'synced';
        } catch (\Throwable $e) {
            $this->error("  [{$media->id}] {$media->file_name}: {$e->getMessage()}");

            return 'failed';
        }
    }

    private function locateSource(Media $media): ?string
    {
        // 老磁盘（public/local）上的文件
        if ($media->disk !== 'uploads') {
            $local = Storage::disk($media->disk);
            $path = $media->getPath();

            if ($local->exists($path)) {
                return $local->path($path);
            }
        }

        // 老 sablog attachments 目录
        $legacyDir = $this->option('legacy-dir');
        $legacyPath = $media->getCustomProperty('legacy_filepath');

        if ($legacyDir && $legacyPath) {
            $candidate = rtrim($legacyDir, '/').'/'.ltrim($legacyPath, '/');

            if (is_file($candidate)) {
                return $candidate;
            }
        }

        return null;
    }

    private function targetPath(Media $media, string $disk): string
    {
        // medialibrary 默认路径约定：{media_id}/{filename}
        return $media->getKey().'/'.$media->file_name;
    }

    private function upload(\Illuminate\Contracts\Filesystem\Filesystem $s3, string $targetPath, string $source): void
    {
        $size = filesize($source);
        $threshold = (int) config('media.multipart_threshold', 50 * 1024 * 1024);

        if ($size > $threshold && method_exists($s3, 'getClient')) {
            // 大文件：AWS SDK MultipartUpload
            $client = $s3->getClient();
            $bucket = $s3->getConfig('bucket');

            $uploader = new \Aws\S3\MultipartUploader($client, $source, [
                'bucket' => $bucket,
                'key' => $targetPath,
                'before_initiate' => function (\Aws\Command $command) use ($source) {
                    $command['ContentType'] = File::mimeType($source) ?: 'application/octet-stream';
                },
            ]);

            $uploader->upload();

            return;
        }

        $s3->putFileAs('', $source, $targetPath, [
            'visibility' => config('media.visibility', 'public'),
            'ContentType' => File::mimeType($source) ?: 'application/octet-stream',
        ]);
    }

    private function markSynced(Media $media, string $disk): void
    {
        $media->update([
            'disk' => $disk,
            'conversions_disk' => $disk,
            'custom_properties' => array_merge($media->custom_properties ?? [], ['pending_sync' => false]),
        ]);
    }
}
