<?php

namespace App\Traits;

use Exception;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\FileBag;
use Symfony\Component\HttpFoundation\ParameterBag;

trait ConvertsBase64ToFiles
{
    protected $onEachBase64FileCallback = null;

    protected function base64FileKeys(): array
    {
        return [];
    }

    /**
     * Helper method to get the body parameters bag.
     *
     * @return \Symfony\Component\HttpFoundation\ParameterBag
     */
    private function bodyParametersBag(): ParameterBag
    {
        return $this->request;
    }

    /**
     * Helper method to get the uploaded files bag.
     *
     * @return FileBag
     */
    private function uploadFilesBag(): FileBag
    {
        return $this->files;
    }

    /**
     * Pulls the Base64 contents for each image key and creates
     * an UploadedFile instance from it and sets it on the
     * request.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        $this->convertBase64ToFiles();
    }

    protected function convertBase64ToFiles()
    {
        $fileKeys = $this->base64FileKeys();

        Collection::make($fileKeys)->each(function ($fileKey, $index) {
            rescue(function () use ($fileKey, $index) {
                $contents = $this->input($fileKey);
                $isSingleFile = gettype($contents) === 'string';
                $preparedContents = $isSingleFile ? [$contents] : $contents;
                $convertedContents = [];
                $onEachBase64FileCallback = $this->getOnEachBase64FileCallback();

                foreach ($preparedContents as $key => $base64Contents) {
                    // Generate a temporary name since base64 encoded strings only includes the file contents.
                    $filename = Str::random();

                    if ($onEachBase64FileCallback) {
                        $onEachBase64FileCallback($fileKey, $base64Contents);
                    }

                    // Generate a temporary path to store the Base64 contents
                    $tempFilePath = tempnam(sys_get_temp_dir(), $filename);

                    // Store the contents using a stream, or by decoding manually
                    if (Str::startsWith($base64Contents, 'data:') && count(explode(',', $base64Contents)) > 1) {
                        $source = fopen($base64Contents, 'r');
                        $destination = fopen($tempFilePath, 'w');

                        stream_copy_to_stream($source, $destination);

                        fclose($source);
                        fclose($destination);
                    } else {
                        file_put_contents($tempFilePath, base64_decode($base64Contents, true));
                    }

                    $uploadedFile = new UploadedFile($tempFilePath, $filename, null, null, true);

                    $convertedContents[] = $uploadedFile;
                }

                $body = $this->bodyParametersBag()->all();
                Arr::forget($body, $fileKey);
                $this->bodyParametersBag()->replace($body);

                $newContents = $isSingleFile ? $convertedContents[0] : $convertedContents;
                $files = $this->uploadFilesBag()->all();
                Arr::set($files, $fileKey, $newContents);
                $this->uploadFilesBag()->replace($files);
            }, rescue: null, report: true);
        });
    }

    protected function convertImgBase64ExistsToFiles()
    {
        $fileKeys = $this->base64FileKeys();

        Collection::make($fileKeys)->each(function ($fileKey, $index) {
            rescue(function () use ($fileKey, $index) {
                $contents = $this->input($fileKey);
                $isSingleFile = gettype($contents) === 'string';
                $preparedContents = $isSingleFile ? [$contents] : $contents;
                $convertedContents = [];

                foreach ($preparedContents as $key => $base64Contents) {
                    try {
                        // Generate a temporary name since base64 encoded strings only includes the file contents.
                        $filename = Str::random();

                        // Generate a temporary path to store the Base64 contents
                        $tempFilePath = tempnam(sys_get_temp_dir(), $filename);

                        // Store the contents using a stream, or by decoding manually
                        if (Str::startsWith($base64Contents, 'data:') && count(explode(',', $base64Contents)) > 1) {
                            $source = fopen($base64Contents, 'r');
                            $destination = fopen($tempFilePath, 'w');

                            stream_copy_to_stream($source, $destination);

                            fclose($source);
                            fclose($destination);
                        } else {
                            file_put_contents($tempFilePath, base64_decode($base64Contents, true));
                        }

                        $uploadedFile = new UploadedFile($tempFilePath, $filename, null, null, true);
                        $convertedContents[] = $uploadedFile;
                    } catch (Exception $e) {
                        $convertedContents[] = $base64Contents;
                        continue;
                    }
                }
                $body = $this->bodyParametersBag()->all();
                Arr::forget($body, $fileKey);
                $this->bodyParametersBag()->replace($body);

                $newContents = $isSingleFile ? $convertedContents[0] : $convertedContents;
                $files = $this->uploadFilesBag()->all();
                Arr::set($files, $fileKey, $newContents);
                $this->uploadFilesBag()->replace($files);
            }, rescue: null, report: true);
        });
    }

    public function onEachBase64File(callable $callback)
    {
        $this->onEachBase64FileCallback = $callback;
    }

    public function getOnEachBase64FileCallback()
    {
        return $this->onEachBase64FileCallback;
    }

    private function is_base64($s)
    {
        return (bool) preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $s);
    }
}
