<?php

namespace Ignite\Core\Files\Encryption;

use Ignite\Core\Files\File;

/**
 * This uses the gpg cli application. It would be nice to use PHP's library for this:
 * https://www.php.net/manual/en/book.gnupg.php. Something we could look into in the future.
 */
class PGPFileEncrypter extends BaseFileEncrypter
{
    /**
     * {@inheritdoc}
     */
    public function encrypt(File $file, ?File $saveTo = null): File
    {
        $this->assertExists($file);
        // If this localizing is something we might do with all encryptors, we could work more with properties on these
        // objects for this and that would make some of this nicer.
        $localFile = $this->localizeFile($file);
        $localSaveTo = $saveTo && $file->is($saveTo) // Overwriting the original file?
            ? $this->makeTemporaryFile() // First save encrypted version to a temporary place.
            : $this->localizeFile($saveTo ?? $this->makeTemporaryFile());

        // --pinentry-mode loopback in order to prevent the GUI popup asking for the pin. This was recommended
        //   somewhere when running this in a non-interactive way. Not sure if it's necessary or if there's a better
        //   setting. More here: https://www.gnupg.org/documentation/manuals/gpgme/Pinentry-Mode.html
        // --batch This turns off interactive mode, I guess. See: https://www.gnupg.org/documentation/manpage.html
        // --yes 'Assume "yes" on most questions.'
        // --quiet 'Try to be as quiet as possible.' Experimentally turning this off when debug is on right now.
        $command = "cat {$localFile->path()} | gpg --pinentry-mode loopback --batch --yes --encrypt ".
            (config('app.debug') ? '' : '--quiet ').
            "--recipient=".$this->getConfig('encryption.recipient')." > {$localSaveTo->path()}";

        passthru($command);

        return $this->moveToFinalDestination($saveTo, $localSaveTo, $file);
    }

    /**
     * {@inheritdoc}
     */
    public function decrypt(File $file, ?File $saveTo = null): File
    {
        $this->assertExists($file);
        $localFile = $this->localizeFile($file);
        $localSaveTo = $saveTo && $file->is($saveTo) // Overwriting the original file?
            ? $this->makeTemporaryFile() // First save decrypted version to a temporary place.
            : $this->localizeFile($saveTo ?? $this->makeTemporaryFile());

        // --pinentry-mode loopback in order to prevent the GUI popup asking for the pin. This was recommended
        //   somewhere when running this in a non-interactive way. Not sure if it's necessary or if there's a better
        //   setting. More here: https://www.gnupg.org/documentation/manuals/gpgme/Pinentry-Mode.html
        // --batch This turns off interactive mode, I guess. See: https://www.gnupg.org/documentation/manpage.html
        // --yes 'Assume "yes" on most questions.'
        // --quiet 'Try to be as quiet as possible.' Experimentally turning this off when debug is on right now.
        //
        // We're redirecting error output to null. I tried to capture this via
        // https://www.php.net/manual/en/book.outcontrol.php, but no luck. I think it'd be ideal to be able to capture
        // that and either log or throw an error when we get some error output.Experimentally turning this off when
        // debug is on right now.
        $command = "gpg --passphrase ".$this->getConfig('decryption.passphrase')." --pinentry-mode loopback --batch ".
            (config('app.debug') ? '' : '--quiet ').
            "--yes --decrypt {$localFile->path()} > {$localSaveTo->path()} ".(config('app.debug') ? '' : '2>/dev/null');

        passthru($command);

        return $this->moveToFinalDestination($saveTo, $localSaveTo, $file);
    }

    /**
     * Move the affected contents to their final destination (the place where the client has asked us to place them).
     *
     * @param File|null $requestedSaveTo The place the client asked us to save the resultant file.
     * @param File $actualSaveTo The place we currently have the resultant file.
     * @param File $originalFile The original unaffected file.
     * @return File
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     */
    protected function moveToFinalDestination(?File $requestedSaveTo, File $actualSaveTo, File $originalFile): File
    {
        if (! $requestedSaveTo || $actualSaveTo->is($requestedSaveTo)) {
            return $actualSaveTo; // We've already saved the result where we needed to.
        }

        if ($originalFile->is($requestedSaveTo)) {
            // We were asked to overwrite the original file. Let's delete the original and copy the contents from the
            // temporary save file. Otherwise, if save-to exists, we'll throw an exception instead of overwriting
            // (in Flysystem 3, we shouldn't need this step since it overwrites instead of throws an error now).
            $requestedSaveTo->delete();
        }

        $actualSaveTo->copyToFile($requestedSaveTo);

        return $requestedSaveTo;
    }
}
