<?php

namespace App\Console\Commands;

use App\Models\Catalog\Product;
use App\Models\Central\Tenant;
use App\Models\Central\TenantSetting;
use App\Models\Sales\Transaction;
use App\Services\CurrentContext;
use App\Services\Storage\TenantStorage;
use Illuminate\Console\Command;

/**
 * Deletes files sitting in a tenant's storage prefix that nothing references
 * anymore (a replaced logo, a product image after the product was deleted, a
 * payment attachment from an abandoned checkout). Defaults to a dry-run
 * report — pass --force to actually delete.
 *
 * Referenced-path detection is deliberately a permissive substring match
 * against every known URL/path-holding column (branding logo, product
 * images, payment-attachment custom fields): a false "still referenced"
 * verdict just leaves a file alone, but a false "orphaned" verdict deletes
 * something in use — so this errs toward keeping files, not removing them.
 */
class CleanupStorageOrphans extends Command
{
    protected $signature = 'storage:cleanup-orphans
        {--tenant= : Only clean up a single tenant id}
        {--force : Actually delete orphaned files instead of reporting them}';

    protected $description = 'Delete tenant storage files no longer referenced by any record';

    public function handle(TenantStorage $storage, CurrentContext $context): int
    {
        $query = Tenant::query();
        if ($id = $this->option('tenant')) {
            $query->whereKey($id);
        }
        $tenants = $query->get();

        $force = (bool) $this->option('force');
        $this->info(($force ? 'Deleting' : 'Scanning (dry run — pass --force to delete)').' orphaned files for '.$tenants->count().' tenant(s)…');

        $totalOrphans = 0;
        $totalBytes = 0;

        foreach ($tenants as $tenant) {
            $context->runForTenant($tenant, function () use ($tenant, $storage, $force, &$totalOrphans, &$totalBytes) {
                $disk = $storage->disk();
                $prefix = $storage->prefix($tenant->id);
                $files = $disk->allFiles($prefix);

                if (empty($files)) {
                    return;
                }

                $referenced = $this->referencedNeedles($tenant);

                foreach ($files as $path) {
                    $isReferenced = false;
                    foreach ($referenced as $needle) {
                        if ($needle !== '' && str_contains($needle, $path)) {
                            $isReferenced = true;
                            break;
                        }
                    }

                    if ($isReferenced) {
                        continue;
                    }

                    $bytes = $disk->size($path);
                    $totalOrphans++;
                    $totalBytes += $bytes;
                    $this->line('  ['.$tenant->name.'] '.($force ? 'deleting' : 'orphaned').': '.$path.' ('.number_format($bytes).' bytes)');

                    if ($force) {
                        $storage->delete($path);
                    }
                }
            });
        }

        $this->info("Done. {$totalOrphans} orphaned file(s), ".number_format($totalBytes).' bytes'.($force ? ' deleted.' : ' found — re-run with --force to delete.'));

        return self::SUCCESS;
    }

    /**
     * Every URL/path value that might reference a stored file, for one tenant.
     *
     * @return list<string>
     */
    private function referencedNeedles(Tenant $tenant): array
    {
        $needles = [];

        /** @var TenantSetting|null $settings */
        $settings = $tenant->settingsRow;
        if ($settings?->branding_logo_url) {
            $needles[] = $settings->branding_logo_url;
        }

        foreach (Product::query()->whereNotNull('image_url')->pluck('image_url') as $url) {
            $needles[] = $url;
        }

        foreach (Transaction::query()->whereNotNull('custom_fields')->pluck('custom_fields') as $fields) {
            foreach ((array) $fields as $value) {
                if (is_string($value)) {
                    $needles[] = $value;
                }
            }
        }

        return $needles;
    }
}
