<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Mtc\MercuryDataModels\Media;

class PurgeStrayMediaFiles extends Command
{
    private Collection $files;
    private Collection $source_folders;
    private Collection $size_folders;

    private $progressBar;

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'media:purge-stray-files';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command checks filesystem for media files that are not accounted in database';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        if (!tenant('id')) {
            $this->output->error('Command needs to be run for tenants `artisan tenants:run media:purge-stray-files`');
            return Command::FAILURE;
        }
        $remove = $this->confirm('CAUTION!! Do you want to remove files? If no, '
            . 'system will provide with details of potential changes instead of removing files');

        $tenant_id = tenant('id');

        $this->size_folders = collect(Storage::disk('media')->directories($tenant_id))
            ->filter(fn ($folder) => preg_match('#[0-9]{2,4}x[0-9]{2,4}#', pathinfo($folder, PATHINFO_BASENAME)));

        $this->source_folders = collect(Storage::disk('media')->directories($tenant_id))
            ->filter(fn ($folder) => preg_match('#202[0-9]\-[A-Za-z]{3}#', pathinfo($folder, PATHINFO_BASENAME)));

        $this->checkStraysInOriginals($remove);
        $this->checkStraysInSizes($remove);
        $this->checkAbandonedSizes();

        return Command::SUCCESS;
    }


    private function isJpgCopy(string $path, int $index): bool
    {
        return str_ends_with($path, '.jpg')
            && substr($path, 0, -4) == substr($this->files[$index + 1] ?? '', 0, -5);
    }

    private function checkStraysInOriginals(bool $remove = false): void
    {
        $this->output->info('Check original locations to see if it contains files that are not in media list');

        $total_originals_to_process = 0;
        $total_size_of_original_files_to_process = 0;

        // Check if file is in database
        foreach ($this->source_folders as $folder) {
            $this->output->info("Checking files in $folder");
            $this->files = collect(Storage::disk('media')->files($folder));
            $stray_files = $this->files->reject(fn ($path, $index) => $this->isJpgCopy($path, $index))
                ->map(fn ($path) => pathinfo($path, PATHINFO_BASENAME))
                ->chunk(100)
                ->map(fn ($chunk) => $this->rejectExistingMedia($chunk, $folder))
                ->flatten(1)
                ->map(fn ($path) => [
                    'path' => $folder,
                    'src' => $path,
                    'size' =>  Storage::disk('media')->size($folder . '/' . $path)
                ]);

            $files_to_process = $stray_files->count();
            $file_size = round($stray_files->sum('size') / (1024 * 1024), 2);
            $this->output->info("Count of originals files in folder: $files_to_process, with size of {$file_size}MB");

            $total_originals_to_process += $files_to_process;
            $total_size_of_original_files_to_process += $file_size;

            if ($remove) {
                $stray_files->each(fn ($entry) => Storage::delete($folder . '/' . $entry['src']));
            }
        }

        $this->output->info("Total count of originals files: $total_originals_to_process, with total size of "
            . $total_size_of_original_files_to_process . 'MB');
    }

    private function checkStraysInSizes(bool $remove = false): void
    {
        $this->output->info('Run through size folders and check if it exists in originals, if not then delete');
        $total_sizes_to_process = 0;

        // Check if file is in database
        foreach ($this->size_folders as $folder) {
            $files = collect(Storage::disk('media')->files($folder));
            $this->output->info("Checking files in $folder - total: " . $files->count());
            $this->progressBar = $this->output->createProgressBar($files->count());
            $removed_files = $files
                ->map(fn ($path) => pathinfo($path, PATHINFO_BASENAME))
                ->chunk(100)
                ->map(fn ($chunk) => $this->rejectExistingSize($chunk))
                ->flatten(1);

            if ($remove && $removed_files->isNotEmpty()) {
                $removed_files->each(fn ($entry) => Storage::delete($folder . '/' . $entry));
            }

            $files_to_process = $removed_files->count();
            $total_sizes_to_process += $files_to_process;
            $this->output->info("Count of stray files in folder: $files_to_process");
        }

        $this->output->info("Total count of stray files: $total_sizes_to_process");
    }

    private function checkAbandonedSizes(): void
    {
        $this->output->info('Run through media to get all assigned sizes and check if image exists in other folders');
        // TODO
    }

    private function rejectExistingMedia(Collection $chunk, $folder): Collection
    {
        $existing = Media::query()
            ->where('path', $folder)
            ->whereIn('src', $chunk)
            ->pluck('src');

        return $chunk->diff($existing);
    }

    private function rejectExistingSize(Collection $chunk): Collection
    {
        $existing = Media::query()
            ->whereIn('src', $chunk)
            ->pluck('src');

        return $chunk->diff($existing);
    }

    private function sizeFileExistsInOriginals(string $path, bool $remove): bool
    {
        $this->progressBar->advance();
        foreach ($this->source_folders as $folder) {
            if (Storage::disk('media')->exists($folder . '/' . pathinfo($path, PATHINFO_BASENAME))) {
                return true;
            }
        }
        if ($remove) {
            Storage::disk('media')->delete($path);
        }
        return false;
    }
}
