<?php

namespace Ignite\Core\Tests\Unit\Files;

use Ignite\Core\Exceptions\Files\SourceFileNotFound;
use Ignite\Core\Files\CsvFileGenerator;
use Ignite\Core\Files\File;
use Ignite\Core\Files\FileTransmitter;
use Ignite\Core\Files\Processors\AdhocFileProcessor;
use Ignite\Core\Files\Processors\DeleteFileProcessor;
use Ignite\Core\Files\Processors\EncryptFileProcessor;
use Ignite\Core\Files\Processors\MoveFileProcessor;
use Ignite\Core\Files\TemporaryFile;
use Ignite\Core\Tests\Support\Exports\SimpleExport;
use Ignite\Core\Tests\TestCase;
use Illuminate\Support\Facades\Storage;
use League\Csv\Reader;

class FileTransmitterTest extends TestCase
{
    /** @test */
    public function copies_files_from_one_place_to_another()
    {
        // Arrange
        Storage::fake();
        $source = new File('source.txt');
        $source->put('source contents');
        $destination = new File('destination.txt');

        // Pre-check
        $source->assertExists();
        $destination->assertMissing();

        // Execute
        (new FileTransmitter())
            ->source($source)
            ->destination($destination)
            ->transmit();

        // Check
        $source->assertExists();
        $destination->assertExists();
        $this->assertEquals($source->get(), $destination->get());
    }

    /** @test */
    public function can_delete_source_after_transmission()
    {
        // Arrange
        Storage::fake();
        $source = new File('source.txt');
        $source->put('source contents');
        $destination = new File('destination.txt');

        // Pre-check
        $source->assertExists();
        $destination->assertMissing();

        // Execute
        (new FileTransmitter())
            ->source($source)
            ->destination($destination)
            ->addPostProcessor('source', new DeleteFileProcessor())
            ->transmit();

        // Check
        $source->assertMissing();
        $destination->assertExists();
    }

    /** @test */
    public function saves_to_temporary_destination_by_default()
    {
        // Arrange
        Storage::fake();
        $source = new File('source.txt');
        $source->put('source contents');

        // Execute
        $destination = (new FileTransmitter())
            ->source($source)
            ->transmit();

        // Check
        $source->assertExists();
        $destination->assertExists();
        $this->assertFalse($source->is($destination));
        $this->assertEquals($source->get(), $destination->get());
        $this->assertInstanceOf(TemporaryFile::class, $destination);
    }

    /** @test */
    public function can_transmit_a_generated_file()
    {
        // Arrange
        Storage::fake();
        $sourceGenerator = new CsvFileGenerator(new SimpleExport());

        // Execute
        $destination = (new FileTransmitter())
            ->source($sourceGenerator)
            ->transmit();

        // Check
        $destination->assertExists();
        $this->assertCount(3, $this->createReaderFromStream($destination->readStream())->getRecords());
        $this->assertEquals('first-last-email-phone', join('-', fgetcsv($destination->readStream())));
    }

    /** @test */
    public function can_specify_post_processors_to_run_on_the_files()
    {
        // Arrange
        Storage::fake();
        $sftp = Storage::fake('some-sftp');
        $source = new File('unprocessed/the-file.txt', 'some-sftp');
        $source->put('the contents');

        // Pre-check
        $this->assertCount(1, $sftp->files('unprocessed'));
        $this->assertCount(0, $sftp->files('processed'));

        // Execute
        $destination = (new FileTransmitter())
            ->source($source)
            ->addPostProcessor('source', new MoveFileProcessor('processed'))
            ->addPostProcessor('source', function (File $file) {
                $file->append('v1');
                return $file;
            })
            ->addPostProcessor('destination', function (File $file) {
                $file->append('value1');
                return $file;
            })
            ->addPostProcessor('destination', new AdhocFileProcessor(function (File $file) {
                $file->append('value2');
                return $file;
            }))
            ->transmit();

        // Check
        $processedSource = File::new('processed/'.$source->getBasename(), $source->getDisk());
        $processedSource->assertExists();
        $source->assertMissing();
        $this->assertCount(0, $sftp->files('unprocessed'));
        $this->assertCount(1, $sftp->files('processed'));
        $this->assertEquals("the contents\nv1", $processedSource->get());
        $this->assertEquals("the contents\nvalue1\nvalue2", $destination->get());
    }

    /** @test */
    public function can_encrypt_before_transferring()
    {
        // Arrange
        Storage::fake();
        $source = new File('the-file.txt');
        $source->put('the contents');
        $destination = new File('encrypted-file.txt.pgp');

        // Execute
        (new FileTransmitter())
            ->source($source)
            ->destination($destination)
            ->addPreProcessor(new EncryptFileProcessor())
            ->transmit();

        // Check
        $this->assertNotEquals($source->get(), $destination->get());
        $this->assertEquals($source->get(), $destination->decrypt()->get());
    }

    /** @test */
    public function can_perform_any_processing_before_it_reaches_its_destination()
    {
        // Arrange
        Storage::fake();
        $source = new File('the-file.txt');
        $source->put('the contents');

        // Execute
        $destination = (new FileTransmitter())
            ->source($source)
            ->addPreProcessor(new AdhocFileProcessor(function (File $file) {
                $file->append('content1');
                return $file;
            }))
            ->addPreProcessor(function (File $file) {
                $file->append('content2');
                return $file;
            })
            ->transmit();

        // Check
        $this->assertEquals("the contents\ncontent1\ncontent2", $destination->get());
    }

    /** @test */
    public function throws_specific_error_when_no_source_file_exists()
    {
        // Set expectation
        $this->expectException(SourceFileNotFound::class);

        // Execute
        (new FileTransmitter())
            ->source(new File('no-place.txt'))
            ->transmit();
    }

    protected function createReaderFromStream($stream): Reader
    {
        return Reader::from($stream);
    }
}
