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

#                                      Copyright 2025 WebPros International, LLC
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited.

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::JSON;

# Pre-load stub modules to prevent 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;

    # Pre-load Cpanel modules
    $INC{'Cpanel/Server/Type.pm'} = 1;

    package Cpanel::Server::Type;
    sub get_products { return { 'cometbackup' => 1 }; }

    $INC{'Cpanel/PwCache.pm'} = 1;

    package Cpanel::PwCache;
    sub new { return bless {}, shift; }

    $INC{'Cpanel/Config/Users.pm'} = 1;

    package Cpanel::Config::Users;
    sub getcpusers { return qw(user1 user2); }

    # Pre-load CometBackup modules with stubs that can be overridden
    $INC{'Whostmgr/CometBackup/Setup.pm'} = 1;

    package Whostmgr::CometBackup::Setup;
    our @EXPORT = qw(is_comet_agent_installed is_agent_logged_in setup_comet_agent install_comet_agent);
    sub is_comet_agent_installed { return 0; }
    sub is_agent_logged_in       { return 0; }
    sub setup_comet_agent        { return 1; }
    sub install_comet_agent      { return 1; }

    $INC{'Whostmgr/CometBackup/AgentManager.pm'} = 1;

    package Whostmgr::CometBackup::AgentManager;
    our @EXPORT = qw(get_status start);
    sub get_status { return ( 1, 'active' ); }
    sub start      { return ( 1, '' ); }

    $INC{'Whostmgr/CometBackup/CometBackupAPI.pm'} = 1;

    package Whostmgr::CometBackup::CometBackupAPI;
    our @EXPORT = qw(get_server_config);
    sub get_server_config { return {}; }

    $INC{'Whostmgr/CometBackup/BackupRunLogs.pm'} = 1;

    package Whostmgr::CometBackup::BackupRunLogs;
    sub new             { return bless {}, shift; }
    sub get_recent_runs { return (); }

    $INC{'Whostmgr/CometBackup/BackupJobs.pm'} = 1;

    package Whostmgr::CometBackup::BackupJobs;
    sub new { return bless {}, shift; }

    sub get_backup_jobs {
        return (
            { job_uuid => 'uuid-1', name => 'Job 1', status => 'active' },
            { job_uuid => 'uuid-2', name => 'Job 2', status => 'disabled' },
        );
    }
}

# Mock the Constants module BEFORE importing CometBackup
BEGIN {
    $INC{'Whostmgr/CometBackup/Constants.pm'} = 1;

    package Whostmgr::CometBackup::Constants;
    our $DISABLED_TOUCHFILE = '/var/cpanel/comet/comet_backup_disabled';
    our $BACKUP_RUNNER_PATH = '/usr/local/cpanel/backup-runner.pl';
    our $RUN_LOG_DB         = '/var/cpanel/comet/backup_run_logs.db';
    1;
}

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

# Mock the logger globally to prevent file access
my $mock_logger_global = Test::MockModule->new('Cpanel::Logger');
$mock_logger_global->redefine(
    'info'  => sub { return 1; },
    'error' => sub { return 1; },
    'warn'  => sub { return 1; },
    'debug' => sub { return 1; }
);

subtest 'get_backup_jobs - returns response with jobs array' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_ok' => sub {
            my ($metadata) = @_;
            $metadata->{ok} = 1;
            return 1;
        },
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $args     = {};
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    ok defined $response,                'Response is defined';
    ok exists $response->{jobs},         'Response has jobs key';
    ok ref $response->{jobs} eq 'ARRAY', 'jobs is an array';

    return;
};

subtest 'get_backup_jobs - GET request with job_ids query param succeeds' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_ok' => sub {
            my ($metadata) = @_;
            $metadata->{ok} = 1;
            return 1;
        },
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $job_ids  = Cpanel::JSON::Dump( [ 'uuid-1', 'uuid-2' ] );
    my $args     = { job_ids => $job_ids };
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    ok defined $response,                'Response is defined';
    ok exists $response->{jobs},         'Response has jobs key';
    ok ref $response->{jobs} eq 'ARRAY', 'jobs is an array';

    return;
};

subtest 'get_backup_jobs - POST request with json body succeeds' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_ok' => sub {
            my ($metadata) = @_;
            $metadata->{ok} = 1;
            return 1;
        },
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $args     = { json => Cpanel::JSON::Dump( { job_uuids => ['uuid-1'] } ) };
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    ok defined $response,        'Response is defined';
    ok exists $response->{jobs}, 'Response has jobs key';

    return;
};

subtest 'get_backup_jobs - job_uuids parameter is not an array' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $args     = { json => Cpanel::JSON::Dump( { job_uuids => 'not an array' } ) };
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    is $metadata->{ok}, 0, 'Metadata is set to not ok';
    like $metadata->{error}, qr/array reference/i, 'Error mentions array reference';
    is scalar( @{ $response->{jobs} } ), 0, 'Returns empty jobs array';

    return;
};

subtest 'get_backup_jobs - job_ids parameter is not an array' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $args     = { json => Cpanel::JSON::Dump( { job_ids => 'not an array' } ) };
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    is $metadata->{ok}, 0, 'Metadata is set to not ok';
    like $metadata->{error}, qr/array reference/i, 'Error mentions array reference';

    return;
};

subtest 'get_backup_jobs - BackupJobs retrieval fails' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $mock_jobs = Test::MockModule->new('Whostmgr::CometBackup::BackupJobs');
    $mock_jobs->redefine(
        'get_backup_jobs' => sub {
            die "Database connection failed";
        }
    );

    my $args     = {};
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    is $metadata->{ok}, 0, 'Metadata is set to not ok';
    like $metadata->{error}, qr/Failed to retrieve backup jobs/i, 'Error mentions retrieval failure';
    is scalar( @{ $response->{jobs} } ), 0, 'Returns empty jobs array on error';

    return;
};

subtest 'get_backup_jobs - with running backup detection' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_ok' => sub {
            my ($metadata) = @_;
            $metadata->{ok} = 1;
            return 1;
        },
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $mock_logs = Test::MockModule->new('Whostmgr::CometBackup::BackupRunLogs');
    $mock_logs->redefine(
        'get_recent_runs' => sub {
            return ( { run_uuid => 'running-uuid-1', status => 'running' } );
        }
    );

    my $mock_file = Test::MockFile->file( '/var/cpanel/comet/backup_run_logs.db', '' );

    my $args     = {};
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    is $metadata->{ok}, 1, 'Metadata is set to ok';
    ok exists $response->{jobs}, 'Response has jobs key';

    return;
};

subtest 'get_backup_jobs - running backup detection fails gracefully' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_ok' => sub {
            my ($metadata) = @_;
            $metadata->{ok} = 1;
            return 1;
        },
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $mock_logs = Test::MockModule->new('Whostmgr::CometBackup::BackupRunLogs');
    $mock_logs->redefine(
        'get_recent_runs' => sub {
            die "Log database error";
        }
    );

    my $mock_file = Test::MockFile->file( '/var/cpanel/comet/backup_run_logs.db', '' );

    my $args     = {};
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    is $metadata->{ok}, 1, 'Metadata is still ok even though log check failed';
    ok exists $response->{jobs}, 'Response still has jobs key';

    return;
};

subtest 'get_backup_jobs - preserves job data returned from BackupJobs' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_ok' => sub {
            my ($metadata) = @_;
            $metadata->{ok} = 1;
            return 1;
        },
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $args     = { json => Cpanel::JSON::Dump( { job_uuids => [] } ) };
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    ok defined $response,                'Response is defined';
    ok exists $response->{jobs},         'Response has jobs key';
    ok ref $response->{jobs} eq 'ARRAY', 'jobs is an array reference';

    return;
};

subtest 'get_backup_jobs - no run_uuid added when no running backups' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_ok' => sub {
            my ($metadata) = @_;
            $metadata->{ok} = 1;
            return 1;
        },
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $mock_jobs = Test::MockModule->new('Whostmgr::CometBackup::BackupJobs');
    $mock_jobs->redefine(
        'get_backup_jobs' => sub {
            return ( { job_uuid => 'uuid-1', name => 'Job 1' } );
        }
    );

    my $mock_logs = Test::MockModule->new('Whostmgr::CometBackup::BackupRunLogs');
    $mock_logs->redefine(
        'get_recent_runs' => sub {
            return ();
        }
    );

    my $mock_file = Test::MockFile->file( '/var/cpanel/comet/backup_run_logs.db', '' );

    my $args     = {};
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    is $metadata->{ok}, 1, 'Metadata is set to ok';
    ok exists $response->{jobs}, 'Response has jobs key';
    is scalar( @{ $response->{jobs} } ), 1, 'Returns 1 job';

    my $job = $response->{jobs}->[0];
    ok !exists $job->{run_uuid}, 'run_uuid not added when no running backup';

    return;
};

subtest 'get_backup_jobs - run_uuid added for running backups' => sub {
    my $mock_utils = Test::MockModule->new('Whostmgr::API::1::Utils');
    $mock_utils->redefine(
        'set_metadata_ok' => sub {
            my ($metadata) = @_;
            $metadata->{ok} = 1;
            return 1;
        },
        'set_metadata_not_ok' => sub {
            my ( $metadata, $error ) = @_;
            $metadata->{ok}    = 0;
            $metadata->{error} = $error;
            return 1;
        }
    );

    my $mock_jobs = Test::MockModule->new('Whostmgr::CometBackup::BackupJobs');
    $mock_jobs->redefine(
        'get_backup_jobs' => sub {
            return ( { job_uuid => 'uuid-1', name => 'Job 1' } );
        }
    );

    my $mock_logs = Test::MockModule->new('Whostmgr::CometBackup::BackupRunLogs');
    $mock_logs->redefine(
        'get_recent_runs' => sub {
            return ( { run_uuid => 'run-uuid-123', status => 'running' } );
        }
    );

    my $mock_file = Test::MockFile->file( '/var/cpanel/comet/backup_run_logs.db', '' );

    my $args     = {};
    my $metadata = {};

    my $response = Whostmgr::API::1::CometBackup::get_backup_jobs( $args, $metadata );

    is $metadata->{ok}, 1, 'Metadata is set to ok';
    ok exists $response->{jobs}, 'Response has jobs key';
    is scalar( @{ $response->{jobs} } ), 1, 'Returns 1 job';

    my $job = $response->{jobs}->[0];
    is $job->{run_uuid}, 'run-uuid-123', 'run_uuid added for running backup';

    return;
};

done_testing();
