#!/usr/local/cpanel/3rdparty/bin/perl

use cPstrict;
use Test::Cpanel::Policy;

use Test2::V0;
use Test2::Tools::Explain;
use Test2::Tools::Compare;
use Test2::Tools::Exception;
use Test2::Plugin::NoWarnings;

use Test::MockModule;
use Test::MockFile;
use Cpanel::TempFile;

# Pre-load stub modules to prevent Setup.pm dependencies from failing
BEGIN {
    $INC{'Cpanel/Plugins/Components/Rules.pm'} = 1;
    $INC{'Cpanel/Plugins/Components/Rules/Util/NVData.pm'} = 1;
    $INC{'Cpanel/Plugins/Components/Rules/CompanyId.pm'} = 1;
}

use Whostmgr::API::1::CometBackup ();
use Cpanel::JSON ();

subtest 'get_backup_destinations' => sub {
    my ( $mocks, $calls ) = setup();

    my ( $args, $metadata ) = ( {}, {} );
    my $response = Whostmgr::API::1::CometBackup::get_backup_destinations( $args, $metadata );
    is $response,
      {
        'destinations' => bag {
            item {
                'id'                  => 'local',
                'recommended'         => 0,
                'size_total'          => match qr/^\d+$/,
                'size_used'           => match qr/^\d+$/,
                'storage_description' => 'Own Storage',
                'storage_name'        => 'Own Storage',
                'storage_path'        => '/cometbackup',
                'storage_type'        => 'local'
            };
            item {
                'id'                  => 'cloud',
                'recommended'         => 1,
                'size_total'          => match qr/^\d+$/,
                'size_used'           => match qr/^\d+$/,
                'storage_description' => 'WebPros Storage',
                'storage_name'        => 'WebPros Storage',
                'storage_type'        => 'cloud'
            };
        }
      },
      'Got the expected data structure from get_backup_destinations';

    is $calls, [], 'Logger warn was not called';
};

subtest 'get_backup_destinations - df command fails' => sub {
    my ( $mocks, $calls ) = setup();
    my $mock_failure = Test::MockModule->new('Whostmgr::API::1::CometBackup');
    $mock_failure->redefine(
        '_get_disk_usage_for_path' => sub {
            my ($backup_path) = @_;
            return 'unexpected output format';
        }
    );

    my ( $args, $metadata ) = ( {}, {} );
    my $response = Whostmgr::API::1::CometBackup::get_backup_destinations( $args, $metadata );

    # Right now in the case of failure to obtain usage info for local (unexpected df output format),
    # it is defaulting to 0 for the sizes. Alternatively, we could hide the local destination
    # altogether in this case, but that would likely cause more confusion.
    is $response,
      {
        'destinations' => bag {
            item {
                'id'                  => 'local',
                'recommended'         => 0,
                'size_total'          => 0,
                'size_used'           => 0,
                'storage_description' => 'Own Storage',
                'storage_name'        => 'Own Storage',
                'storage_path'        => '/cometbackup',
                'storage_type'        => 'local'
            };
            item {
                'id'                  => 'cloud',
                'recommended'         => 1,
                'size_total'          => match qr/^\d+$/,
                'size_used'           => match qr/^\d+$/,
                'storage_description' => 'WebPros Storage',
                'storage_name'        => 'WebPros Storage',
                'storage_type'        => 'cloud'
            };
        }
      },
      'Returns zeros for disk usage when df output cannot be parsed';
};

subtest 'delete_backup_job - missing parameter' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response;
     {
         my $warnings = warnings { $response = Whostmgr::API::1::CometBackup::delete_backup_job( $args, $metadata ); };
     }
     
     ok exists $response->{deleted}, 'Response has deleted key';
     
     return;
};

subtest 'disable_backup_job - missing parameter' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response;
     {
         my $warnings = warnings { $response = Whostmgr::API::1::CometBackup::disable_backup_job( $args, $metadata ); };
     }
     
     ok exists $response->{disabled}, 'Response has disabled key';
     
     return;
};

subtest 'enable_backup_job - missing parameter' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response;
     {
         my $warnings = warnings { $response = Whostmgr::API::1::CometBackup::enable_backup_job( $args, $metadata ); };
     }
     
     ok exists $response->{enabled}, 'Response has enabled key';
     
     return;
};

subtest 'run_backup_job - missing parameter' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response;
     {
         my $warnings = warnings { $response = Whostmgr::API::1::CometBackup::run_backup_job( $args, $metadata ); };
     }
     
     is $response->{started}, 0, 'Started is 0 on error';
     
     return;
};

subtest 'download_backup_job_log - missing job_uuid' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response = Whostmgr::API::1::CometBackup::download_backup_job_log( $args, $metadata );
     
     is $response->{log}, '', 'Log is empty on error';
     ok exists $response->{error}, 'Error key exists';
     
     return;
};

subtest 'download_restore_job_log - missing run_uuid' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response = Whostmgr::API::1::CometBackup::download_restore_job_log( $args, $metadata );
     
     is $response->{log}, '', 'Log is empty on error';
     ok exists $response->{error}, 'Error key exists';
     
     return;
};

subtest 'cancel_running_backup_job - missing parameter' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response = Whostmgr::API::1::CometBackup::cancel_running_backup_job( $args, $metadata );
     
     is $response->{cancelled}, {}, 'Cancelled is empty hash on error';
     
     return;
};

subtest 'APPLIST structure' => sub {
     my $applist = Whostmgr::API::1::CometBackup::APPLIST();
     
     ok exists $applist->{get_backup_destinations}, 'get_backup_destinations in APPLIST';
     ok exists $applist->{get_backup_jobs}, 'get_backup_jobs in APPLIST';
     ok exists $applist->{create_backup_job}, 'create_backup_job in APPLIST';
     ok exists $applist->{delete_backup_job}, 'delete_backup_job in APPLIST';
     
     return;
};

subtest 'NEEDS_ROLE constant' => sub {
     my $needs_role = Whostmgr::API::1::CometBackup::NEEDS_ROLE();
     
     ok ref($needs_role) eq 'HASH', 'NEEDS_ROLE returns a hash';
     ok exists $needs_role->{get_backup_destinations}, 'get_backup_destinations in NEEDS_ROLE';
     ok exists $needs_role->{create_backup_job}, 'create_backup_job in NEEDS_ROLE';
     
     return;
};

subtest '_decode_json_args - missing json' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {};
     
     my ( $status, $decoded_args ) = Whostmgr::API::1::CometBackup::_decode_json_args( $metadata, $args );
     
     is $status, 0, 'Status is 0 when json parameter missing';
     
     return;
};

subtest 'list_restore_history - no database' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {};
     
     my $response = Whostmgr::API::1::CometBackup::list_restore_history( $args, $metadata );
     
     ok exists $response->{history}, 'Response has history key';
     
     return;
};

subtest 'run_restore_job - missing type parameter' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response = Whostmgr::API::1::CometBackup::run_restore_job( $args, $metadata );
     
     is ref($response->{job}), 'HASH', 'Job is a hash';
     
     return;
};

subtest 'get_restore_log - missing job_uuid' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response = Whostmgr::API::1::CometBackup::get_restore_log( $args, $metadata );
     
     is $response->{log}, '', 'Log is empty on error';
     
     return;
};

subtest 'duplicate_backup_job - missing job_uuid' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response;
     {
         my $warnings = warnings { $response = Whostmgr::API::1::CometBackup::duplicate_backup_job( $args, $metadata ); };
     }
     
     is ref($response->{job}), 'HASH', 'Job is a hash';
     
     return;
};

subtest 'list_accounts_for_restore - invalid mode' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({ mode => 'invalid' }),
     };
     
     my $response = Whostmgr::API::1::CometBackup::list_accounts_for_restore( $args, $metadata );
     
     is ref($response->{accounts}), 'ARRAY', 'Accounts is an array even on error';
     
     return;
};

subtest 'list_restore_history - no database' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {};
     
     my $response = Whostmgr::API::1::CometBackup::list_restore_history( $args, $metadata );
     
     ok exists $response->{history}, 'Response has history key';
     is ref($response->{history}), 'ARRAY', 'History is an array';
     
     return;
};

subtest 'modify_backup_job - missing parameters' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response;
     {
         my $warnings = warnings { $response = Whostmgr::API::1::CometBackup::modify_backup_job( $args, $metadata ); };
     }
     
     is ref($response->{job}), 'HASH', 'Job is a hash';
     
     return;
};

subtest 'upload_credentials - missing credentials' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response = Whostmgr::API::1::CometBackup::upload_credentials( $args, $metadata );
     
     is $response->{uploaded}, 0, 'Upload fails when credentials missing';
     
     return;
};

subtest 'run_restore_job - missing parameters' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response = Whostmgr::API::1::CometBackup::run_restore_job( $args, $metadata );
     
     is ref($response->{job}), 'HASH', 'Job is a hash on error';
     
     return;
};

subtest 'get_backup_jobs - missing json parameter' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {};
     
     my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );
     
     is ref($response->{jobs}), 'ARRAY', 'Jobs is an array';
     
     return;
};

subtest '_format_duration - hours and minutes' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_duration(3661);
     like $result, qr/hour/, 'Contains hour in duration';
     
     return;
};

subtest '_format_duration - only minutes' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_duration(300);
     is $result, '5 minutes', 'Formats minutes correctly';
     
     return;
};

subtest '_format_duration - only seconds' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_duration(45);
     is $result, '45 seconds', 'Formats seconds correctly';
     
     return;
};

subtest '_format_duration - zero duration' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_duration(0);
     is $result, '', 'Returns empty string for zero duration';
     
     return;
};

subtest '_format_duration - undefined duration' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_duration(undef);
     is $result, '', 'Returns empty string for undefined duration';
     
     return;
};

subtest '_format_bytes - gigabytes' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_bytes(1073741824);
     like $result, qr/^1\.0GB$/, 'Formats 1GB correctly';
     
     return;
};

subtest '_format_bytes - megabytes' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_bytes(524288000);
     like $result, qr/MB$/, 'Formats megabytes correctly';
     
     return;
};

subtest '_format_bytes - zero bytes' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_bytes(0);
     is $result, '0B', 'Returns 0B for zero bytes';
     
     return;
};

subtest '_format_bytes - undefined bytes' => sub {
     my $result = Whostmgr::API::1::CometBackup::_format_bytes(undef);
     is $result, '', 'Returns empty string for undefined bytes';
     
     return;
};

subtest '_normalize_backup_options - enable databases' => sub {
     my $backup_options = { databases => 1 };
     Whostmgr::API::1::CometBackup::_normalize_backup_options( $backup_options );
     
     is $backup_options->{databases}, {
         mysql => 1,
         postgresql => 1,
     }, 'Expands databases flag to hash';
     
     return;
};

subtest '_normalize_backup_options - disable databases' => sub {
     my $backup_options = { databases => 0 };
     Whostmgr::API::1::CometBackup::_normalize_backup_options( $backup_options );
     
     is $backup_options->{databases}, {
         mysql => 0,
         postgresql => 0,
     }, 'Expands disabled databases flag to hash';
     
     return;
};

subtest '_normalize_backup_options - already hash' => sub {
     my $backup_options = { 
         databases => { mysql => 1, postgresql => 0 }
     };
     my $original = Cpanel::JSON::Dump( $backup_options );
     Whostmgr::API::1::CometBackup::_normalize_backup_options( $backup_options );
     
     is Cpanel::JSON::Dump( $backup_options ), $original, 'Leaves hash databases unchanged';
     
     return;
};

subtest '_get_users_to_backup - all_accounts' => sub {
     my ( $mocks, $calls ) = setup();
     
     push @$mocks, Test::MockModule->new('Cpanel::Config::Users');
     $mocks->[-1]->redefine(
         'getcpusers' => sub { return qw(user1 user2 user3); }
     );
     
     my $job_config = { all_accounts => 1 };
     my @users = Whostmgr::API::1::CometBackup::_get_users_to_backup( $job_config );
     
     is \@users, bag { item 'user1'; item 'user2'; item 'user3'; end(); }, 'Returns all cPanel users';
     
     return;
};

subtest '_get_users_to_backup - specific accounts' => sub {
     my $job_config = { 
         all_accounts => 0,
         accounts => [ 'user1', 'user2' ]
     };
     my @users = Whostmgr::API::1::CometBackup::_get_users_to_backup( $job_config );
     
     is \@users, bag { item 'user1'; item 'user2'; end(); }, 'Returns specified users only';
     
     return;
};

subtest 'get_local_info - path exists and is directory' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({ local_path => '/test/path' }),
     };
     
     my $response = Whostmgr::API::1::CometBackup::get_local_info( $args, $metadata );
     
     is $response,
       {
         size_used => match qr/^\d+$/,
         size_total => match qr/^\d+$/,
         exists => match qr/^[01]$/,
         is_dir => match qr/^[01]$/,
       },
       'Returns correct structure for local info';
     
     return;
};

subtest 'get_local_info - missing parameter' => sub {
     my ( $mocks, $calls ) = setup();
     
     my $metadata = {};
     my $args = {
         json => Cpanel::JSON::Dump({}),
     };
     
     my $response = Whostmgr::API::1::CometBackup::get_local_info( $args, $metadata );
     
     is $response,
       {
         size_used => 0,
         size_total => 0,
         exists => 0,
         is_dir => 0,
       },
       'Returns default values when local_path missing';
     
     return;
};

done_testing();

sub setup {
    my ( @mocks, @calls );

    # Mock error_log file
    push @mocks, Test::MockFile->file('/usr/local/cpanel/logs/error_log');

    push @mocks, Test::MockModule->new('Cpanel::Logger');
    $mocks[-1]->redefine(
        'warn' => sub {
            my ( $obj, $msg ) = @_;
            push @calls, [ 'logger warn', $msg ];
            return 1;
        }
    );
    $mocks[-1]->redefine(
        'error' => sub {
            my ( $obj, $msg ) = @_;
            push @calls, [ 'logger error', $msg ];
            return 1;
        }
    );

    push @mocks, Test::MockModule->new('Whostmgr::API::1::CometBackup');
    $mocks[-1]->redefine(
        '_get_disk_usage_for_path' => sub {
            my ($backup_path) = @_;
            return '/dev/vda37        85152739328 59698384896 25454354432  71% /';
        }
    );

    push @mocks, Test::MockModule->new('Whostmgr::CometBackup::CometBackupAPI');
    $mocks[-1]->redefine(
        'make_server_request' => sub {
            my ( $endpoint, $params ) = @_;
            return {
                Status  => 200,
                Profile => {
                    Destinations => {
                        'cloud' => {
                            DestinationType => 1000,
                            Description     => 'WebPros Storage',
                            Statistics      => {
                                ClientProvidedSize => {
                                    Size => 1234567890
                                }
                            },
                            StorageLimitEnabled => 1,
                            StorageLimitBytes   => 9876543210
                        },
                        'local' => {
                            DestinationType => 1002,
                            Description     => 'Own Storage',
                            LocalcopyPath   => '/cometbackup',
                        }
                    }
                }
            };
        }
    );
    $mocks[-1]->redefine(
        'get_server_config' => sub {
            return { username => 'testuser' };
        }
    );

    # Mock the Setup module's ensure_recovery_data_protected_item function
    push @mocks, Test::MockModule->new('Whostmgr::CometBackup::Setup');
    $mocks[-1]->redefine(
        'ensure_recovery_data_protected_item' => sub {
            return 1;
        }
    );

    return ( \@mocks, \@calls );
}
