####################################################################################################################################
# Tests for Storage::Local module
####################################################################################################################################
package pgBackRestTest::Module::Storage::StoragePerlTest;
use parent 'pgBackRestTest::Common::RunTest';

####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';

use pgBackRest::Config::Config;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::LibC qw(:crypto);
use pgBackRest::Storage::Base;

use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
use pgBackRestTest::Env::Host::HostBackupTest;

####################################################################################################################################
# run
####################################################################################################################################
sub run
{
    my $self = shift;

    # Define test file
    my $strFile = $self->testPath() . '/file.txt';
    my $strFileCopy = $self->testPath() . '/file.txt.copy';
    my $strFileHash = 'bbbcf2c59433f68f22376cd2439d6cd309378df6';
    my $strFileContent = 'TESTDATA';
    my $iFileSize = length($strFileContent);

    # Create local storage
    $self->{oStorageLocal} = new pgBackRest::Storage::Storage('<LOCAL>');

    ################################################################################################################################
    if ($self->begin("pathGet()"))
    {
        $self->testResult(sub {$self->storageLocal()->pathGet('file')}, '/file', 'relative path');
        $self->testResult(sub {$self->storageLocal()->pathGet('/file2')}, '/file2', 'absolute path');
    }

    ################################################################################################################################
    if ($self->begin('put()'))
    {
        #---------------------------------------------------------------------------------------------------------------------------
        $self->testResult(
            sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile))}, 0, 'put empty');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->testResult(
            sub {$self->storageLocal()->put($strFile)}, 0, 'put empty (all defaults)');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->testResult(
            sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile), $strFileContent)}, $iFileSize, 'put');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->testResult(
            sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile), \$strFileContent)}, $iFileSize,
            'put reference');
    }

    ################################################################################################################################
    if ($self->begin('get()'))
    {
        #---------------------------------------------------------------------------------------------------------------------------
        $self->testResult(
            sub {$self->storageLocal()->get($self->storageLocal()->openRead($strFile, {bIgnoreMissing => true}))}, undef,
            'get missing');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->storageLocal()->put($strFile);
        $self->testResult(sub {${$self->storageLocal()->get($strFile)}}, undef, 'get empty');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->storageLocal()->put($strFile, $strFileContent);
        $self->testResult(sub {${$self->storageLocal()->get($strFile)}}, $strFileContent, 'get');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->testResult(
            sub {${$self->storageLocal()->get($self->storageLocal()->openRead($strFile))}}, $strFileContent, 'get from io');
    }

    ################################################################################################################################
    if ($self->begin('hashSize()'))
    {
        $self->testResult(
            sub {$self->storageLocal()->put($strFile, $strFileContent)}, 8, 'put');

        $self->testResult(
            sub {$self->storageLocal()->hashSize($strFile)},
            qw{(} . cryptoHashOne('sha1', $strFileContent) . ', ' . $iFileSize . qw{)}, '    check hash/size');
        $self->testResult(
            sub {$self->storageLocal()->hashSize(BOGUS, {bIgnoreMissing => true})}, "([undef], [undef])",
            '    check missing hash/size');
    }

    ################################################################################################################################
    if ($self->begin('copy()'))
    {
        #---------------------------------------------------------------------------------------------------------------------------
        $self->testException(
            sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, ERROR_FILE_MISSING,
            "unable to open missing file '${strFile}' for read");
        $self->testResult(
            sub {$self->storageLocal()->exists($strFileCopy)}, false, '   destination does not exist');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->testResult(
            sub {$self->storageLocal()->copy(
                $self->storageLocal()->openRead($strFile, {bIgnoreMissing => true}),
                $self->storageLocal()->openWrite($strFileCopy))},
            false, 'missing source io');
        $self->testResult(
            sub {$self->storageLocal()->exists($strFileCopy)}, false, '   destination does not exist');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->testException(
            sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, ERROR_FILE_MISSING,
            "unable to open missing file '${strFile}' for read");

        #---------------------------------------------------------------------------------------------------------------------------
        $self->storageLocal()->put($strFile, $strFileContent);

        $self->testResult(sub {$self->storageLocal()->copy($strFile, $strFileCopy)}, true, 'copy filename->filename');
        $self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, '    check copy');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->storageLocal()->remove($strFileCopy);

        $self->testResult(
            sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, true, 'copy io->filename');
        $self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, '    check copy');

        #---------------------------------------------------------------------------------------------------------------------------
        $self->storageLocal()->remove($strFileCopy);

        $self->testResult(
            sub {$self->storageLocal()->copy(
                $self->storageLocal()->openRead($strFile), $self->storageLocal()->openWrite($strFileCopy))},
            true, 'copy io->io');
        $self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, '    check copy');
    }

    ################################################################################################################################
    if ($self->begin('exists()'))
    {
        $self->storageLocal()->put($self->testPath() . "/test.file");

        $self->testResult(sub {$self->storageLocal()->exists($self->testPath() . "/test.file")}, true, 'existing file');
        $self->testResult(sub {$self->storageLocal()->exists($self->testPath() . "/test.missing")}, false, 'missing file');
        $self->testResult(sub {$self->storageLocal()->exists($self->testPath())}, false, 'path');
    }

    ################################################################################################################################
    if ($self->begin('info()'))
    {
        $self->testResult(
            sub {$self->storageLocal()->info($self->testPath())},
            "{group => " . $self->group() . ", mode => 0770, type => d, user => " . $self->pgUser() . "}",
            'stat dir successfully');

        $self->testException(sub {$self->storageLocal()->info(BOGUS)}, ERROR_FILE_OPEN,
            "unable to get info for missing path/file '/bogus'");
    }

    ################################################################################################################################
    if ($self->begin("manifest() and list()"))
    {
        #---------------------------------------------------------------------------------------------------------------------------
        $self->testException(
            sub {$self->storageLocal()->manifest($self->testPath() . '/missing')},
            ERROR_PATH_MISSING, "unable to list file info for missing path '" . $self->testPath() . "/missing'");

        #---------------------------------------------------------------------------------------------------------------------------
        # Setup test data
        executeTest('mkdir -m 750 ' . $self->testPath() . '/sub1');
        executeTest('mkdir -m 750 ' . $self->testPath() . '/sub1/sub2');
        executeTest('mkdir -m 750 ' . $self->testPath() . '/sub2');

        executeTest("echo 'TESTDATA' > " . $self->testPath() . '/test.txt');
        utime(1111111111, 1111111111, $self->testPath() . '/test.txt');
        executeTest('chmod 1640 ' . $self->testPath() . '/test.txt');

        executeTest("echo 'TESTDATA_' > ". $self->testPath() . '/sub1/test-sub1.txt');
        utime(1111111112, 1111111112, $self->testPath() . '/sub1/test-sub1.txt');
        executeTest('chmod 0640 ' . $self->testPath() . '/sub1/test-sub1.txt');

        executeTest("echo 'TESTDATA_' > ". $self->testPath() . '/sub1/.test-sub1.txt');
        utime(1111111112, 1111111112, $self->testPath() . '/sub1/.test-sub1.txt');
        executeTest('chmod 0640 ' . $self->testPath() . '/sub1/.test-sub1.txt');

        executeTest("echo 'TESTDATA__' > " . $self->testPath() . '/sub1/sub2/test-sub2.txt');
        utime(1111111113, 1111111113, $self->testPath() . '/sub1/sub2/test-sub2.txt');
        executeTest('chmod 0646 ' . $self->testPath() . '/sub1/sub2/test-sub2.txt');

        executeTest('mkfifo -m 606 ' . $self->testPath() . '/sub1/apipe');

        executeTest('ln ' . $self->testPath() . '/test.txt ' . $self->testPath() . '/sub1/test-hardlink.txt');
        executeTest('ln ' . $self->testPath() . '/test.txt ' . $self->testPath() . '/sub1/sub2/test-hardlink.txt');

        executeTest('ln -s .. ' . $self->testPath() . '/sub1/test');
        executeTest('chmod 0700 ' . $self->testPath() . '/sub1/test');
        executeTest('ln -s ../.. ' . $self->testPath() . '/sub1/sub2/test');
        executeTest('chmod 0750 ' . $self->testPath() . '/sub1/sub2/test');

        executeTest('chmod 0770 ' . $self->testPath());

        $self->testResult(
            sub {$self->storageLocal()->manifest($self->testPath())},
            '{. => {group => ' . $self->group() . ', mode => 0770, type => d, user => ' . $self->pgUser() . '}, ' .
            'sub1 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
            'sub1/.test-sub1.txt => ' .
                '{group => ' . $self->group() . ', mode => 0640, modification_time => 1111111112, size => 10, type => f, user => ' .
                $self->pgUser() . '}, ' .
            'sub1/apipe => {group => ' . $self->group() . ', mode => 0606, type => s, user => ' . $self->pgUser() . '}, ' .
            'sub1/sub2 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
            'sub1/sub2/test => {group => ' . $self->group() . ', link_destination => ../.., type => l, user => ' .
                $self->pgUser() . '}, ' .
            'sub1/sub2/test-hardlink.txt => ' .
                '{group => ' . $self->group() . ', mode => 0640, modification_time => 1111111111, size => 9, type => f, user => ' .
                $self->pgUser() . '}, ' .
            'sub1/sub2/test-sub2.txt => ' .
                '{group => ' . $self->group() . ', mode => 0646, modification_time => 1111111113, size => 11, type => f, user => ' .
                $self->pgUser() . '}, ' .
            'sub1/test => {group => ' . $self->group() . ', link_destination => .., type => l, user => ' . $self->pgUser() . '}, ' .
            'sub1/test-hardlink.txt => ' .
                '{group => ' . $self->group() . ', mode => 0640, modification_time => 1111111111, size => 9, type => f, user => ' .
                $self->pgUser() . '}, ' .
            'sub1/test-sub1.txt => ' .
                '{group => ' . $self->group() . ', mode => 0640, modification_time => 1111111112, size => 10, type => f, user => ' .
                $self->pgUser() . '}, ' .
            'sub2 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
            'test.txt => ' .
                '{group => ' . $self->group() . ', mode => 0640, modification_time => 1111111111, size => 9, type => f, user => ' .
                $self->pgUser() . '}}',
            'complete manifest');

        $self->testResult(sub {$self->storageLocal()->list($self->testPath())}, "(sub1, sub2, test.txt)", "list");
        $self->testResult(sub {$self->storageLocal()->list($self->testPath(), {strExpression => "2\$"})}, "sub2", "list");
        $self->testResult(
            sub {$self->storageLocal()->list($self->testPath(), {strSortOrder => 'reverse'})}, "(test.txt, sub2, sub1)",
            "list reverse");
        $self->testResult(sub {$self->storageLocal()->list($self->testPath() . "/sub2")}, "[undef]", "list empty");
        $self->testResult(
            sub {$self->storageLocal()->list($self->testPath() . "/sub99", {bIgnoreMissing => true})}, "[undef]", "list missing");
        $self->testException(
            sub {$self->storageLocal()->list($self->testPath() . "/sub99")}, ERROR_PATH_MISSING,
            "unable to list files for missing path '" . $self->testPath() . "/sub99'");
    }

    ################################################################################################################################
    if ($self->begin('move()'))
    {
        my $strFileCopy = "${strFile}.copy";
        my $strFileSub = $self->testPath() . '/sub/file.txt';

        #---------------------------------------------------------------------------------------------------------------------------
        $self->testException(
            sub {$self->storageLocal()->move($strFile, $strFileCopy)}, ERROR_FILE_MOVE,
            "unable to move '${strFile}' to '${strFile}.copy': No such file or directory");
    }

    ################################################################################################################################
    if ($self->begin('owner()'))
    {
        my $strFile = $self->testPath() . "/test.txt";

        $self->testException(
            sub {$self->storageLocal()->owner($strFile, 'root')}, ERROR_FILE_MISSING,
            "unable to stat '${strFile}': No such file or directory");

        executeTest("touch ${strFile}");

        $self->testException(
            sub {$self->storageLocal()->owner($strFile, BOGUS)}, ERROR_FILE_OWNER,
            "unable to set ownership for '${strFile}' because user 'bogus' does not exist");
        $self->testException(
            sub {$self->storageLocal()->owner($strFile, undef, BOGUS)}, ERROR_FILE_OWNER,
            "unable to set ownership for '${strFile}' because group 'bogus' does not exist");

        $self->testResult(sub {$self->storageLocal()->owner($strFile)}, undef, "no ownership changes");
        $self->testResult(sub {$self->storageLocal()->owner($strFile, TEST_USER)}, undef, "same user");
        $self->testResult(sub {$self->storageLocal()->owner($strFile, undef, TEST_GROUP)}, undef, "same group");
        $self->testResult(
            sub {$self->storageLocal()->owner($strFile, TEST_USER, TEST_GROUP)}, undef,
            "same user, group");

        $self->testException(
            sub {$self->storageLocal()->owner($strFile, 'root', undef)}, ERROR_FILE_OWNER,
            "unable to set ownership for '${strFile}': Operation not permitted");
        $self->testException(
            sub {$self->storageLocal()->owner($strFile, undef, 'root')}, ERROR_FILE_OWNER,
            "unable to set ownership for '${strFile}': Operation not permitted");

        executeTest("sudo chown :root ${strFile}");
        $self->testResult(
            sub {$self->storageLocal()->owner($strFile, undef, TEST_GROUP)}, undef, "change group back from root");
    }

    ################################################################################################################################
    if ($self->begin('pathCreate()'))
    {
        my $strTestPath = $self->testPath() . "/" . BOGUS;

        $self->testResult(sub {$self->storageLocal()->pathCreate($strTestPath)}, "[undef]",
            "test creation of path " . $strTestPath);

        $self->testException(sub {$self->storageLocal()->pathCreate($strTestPath)}, ERROR_PATH_CREATE,
            "unable to create path '". $strTestPath. "'");

        $self->testResult(sub {$self->storageLocal()->pathCreate($strTestPath, {bIgnoreExists => true})}, "[undef]",
            "ignore path exists");
    }

    ################################################################################################################################
    if ($self->begin('pathExists()'))
    {
        $self->storageLocal()->put($self->testPath() . "/test.file");

        $self->testResult(sub {$self->storageLocal()->pathExists($self->testPath() . "/test.file")}, false, 'existing file');
        $self->testResult(sub {$self->storageLocal()->pathExists($self->testPath() . "/test.missing")}, false, 'missing file');
        $self->testResult(sub {$self->storageLocal()->pathExists($self->testPath())}, true, 'path');
    }

    ################################################################################################################################
    if ($self->begin('pathSync()'))
    {
        $self->testResult(sub {$self->storageLocal()->pathSync($self->testPath())}, "[undef]", "test path sync");
    }
}

####################################################################################################################################
# Getters
####################################################################################################################################
# sub host {return '127.0.0.1'}
# sub pathLocal {return shift->{strPathLocal}};
# sub pathRemote {return shift->{strPathRemote}};
sub storageLocal {return shift->{oStorageLocal}};
# sub storageEncrypt {return shift->{oStorageEncrypt}};
# sub storageRemote {return shift->{oStorageRemote}};

1;