package Whostmgr::CometBackup::Constants;

#                                      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.

our $CREDENTIALS_FILE      = '/var/cpanel/comet/server_credentials.json';
our $BACKUP_JOBS_DB        = '/var/cpanel/comet/backup_jobs.db';
our $PROTECTED_ITEMS_DB    = '/var/cpanel/comet/protected_items.db';
our $PROTECTED_ITEM_MAP_DB = '/var/cpanel/comet/protected_items.db';
our $BACKUP_TOOL_PATH      = '/opt/CometBackup/backup-tool';

# License related
our $LICENSE_ACTIVATION_FILE  = '/var/cpanel/extended_auth/storage/comet';
our $WPA_JWT_FILE             = '/var/cpanel/comet/wpa_jwt.token';
our $COMET_ACCOUNT_PORTAL_URL = 'https://account.cometbackup.com';

# Recovery data constants
our $RECOVERY_DATA_PATH      = '/var/cpanel/comet/';    # Path to backup for disaster recovery
our $RECOVERY_DATA_ITEM_NAME = 'recovery_data';         # Special Protected Item name for recovery data

# Cache directory and files
our $CACHE_DIR         = '/var/cpanel/comet/cache';
our $REGION_DATA_CACHE = '/var/cpanel/comet/cache/region_data.json';

# Backup runner constants
our $BACKUP_RUNNER_PATH  = '/usr/local/cpanel/bin/comet_backup_runner';
our $RUN_LOG_DB          = '/var/cpanel/comet/backup_run_logs.db';
our $PID_FILE            = '/var/run/comet_backup_runner.pid';
our $CANCEL_FILE_PATTERN = '/var/run/comet_backup_cancel_%s';             # %s will be replaced with job UUID
our $DISABLED_TOUCHFILE  = '/var/cpanel/comet/comet_backup_disabled';     # Indicates Comet Backup functions are disabled
our $TEXT_LOG_DIR        = '/var/log/comet_backup_runs';                  # Directory for dated text log files
our $PKGACCT_LOCK_DIR    = '/var/run/comet_pkgacct_locks';                # Directory for per-user pkgacct lock files
our $MAX_PARALLEL_USERS  = 3;                                             # Maximum users to backup in parallel
our $MAX_LOAD_AVERAGE    = 5.0;                                           # Maximum system load before throttling

# Protected Item Types
use constant PROTECTED_ITEM_TYPES => qw(panel_config homedir databases);

# =============================================================================
# Comet API Job Status Constants
# See: https://docs.cometbackup.com/latest/api/api-constants/#job
# =============================================================================

# Job Status: Success range (5xxx) - job stopped for a successful reason
use constant JOB_STATUS_STOP_SUCCESS__MIN => 5000;
use constant JOB_STATUS_STOP_SUCCESS      => 5000;    # The job is complete and was successful
use constant JOB_STATUS_STOP_SUCCESS__MAX => 5999;

# Job Status: Running range (6xxx) - job is still running
use constant JOB_STATUS_RUNNING__MIN          => 6000;
use constant JOB_STATUS_RUNNING_INDETERMINATE => 6000;    # Unused
use constant JOB_STATUS_RUNNING_ACTIVE        => 6001;    # Job is currently running
use constant JOB_STATUS_RUNNING_REVIVED       => 6002;    # Job was abandoned but is now running again
use constant JOB_STATUS_RUNNING_TRYAGAIN      => 6003;    # Job encountered error, waiting to retry
use constant JOB_STATUS_NOT_YET_STARTED       => 6004;    # Job created but not yet initialized by client
use constant JOB_STATUS_RUNNING__MAX          => 6999;

# Job Status: Failed range (7xxx) - job stopped for an unsuccessful reason
use constant JOB_STATUS_FAILED__MIN               => 7000;
use constant JOB_STATUS_FAILED_TIMEOUT            => 7000;    # Job timed out
use constant JOB_STATUS_FAILED_WARNING            => 7001;    # Completed with warnings/issues
use constant JOB_STATUS_FAILED_ERROR              => 7002;    # Error during job, did not complete
use constant JOB_STATUS_FAILED_QUOTA              => 7003;    # Quota exceeded
use constant JOB_STATUS_FAILED_SCHEDULEMISSED     => 7004;    # Did not start at scheduled time
use constant JOB_STATUS_FAILED_CANCELLED          => 7005;    # Cancelled manually or time limit reached
use constant JOB_STATUS_FAILED_SKIPALREADYRUNNING => 7006;    # Skipped because already running
use constant JOB_STATUS_FAILED_ABANDONED          => 7007;    # Stopped unexpectedly or marked abandoned
use constant JOB_STATUS_FAILED__MAX               => 7999;

# Job Classification Constants
# See: https://docs.cometbackup.com/latest/api/api-constants/#job
use constant JOB_CLASSIFICATION_BACKUP         => 4000;
use constant JOB_CLASSIFICATION_RESTORE        => 4001;
use constant JOB_CLASSIFICATION_RESTORE_CUSTOM => 4002;    # Restore to custom destination
use constant JOB_CLASSIFICATION_REMEASURE      => 4003;
use constant JOB_CLASSIFICATION_DEEPVERIFY     => 4004;
use constant JOB_CLASSIFICATION_RETENTION      => 4005;
use constant JOB_CLASSIFICATION_UNLOCK         => 4006;
use constant JOB_CLASSIFICATION_DELETE_CUSTOM  => 4007;
use constant JOB_CLASSIFICATION_IMPORT         => 4008;
use constant JOB_CLASSIFICATION_REPAIR_INDEXES => 4009;

# =============================================================================
# Helper Functions for Job Status
# =============================================================================

=head2 is_job_running

Returns true if the job status indicates the job is still running.

    if (Whostmgr::CometBackup::Constants::is_job_running($status)) {
        # Job is still running
    }

=cut

sub is_job_running {
    my ($status) = @_;
    return ( $status >= JOB_STATUS_RUNNING__MIN && $status <= JOB_STATUS_RUNNING__MAX );
}

=head2 is_job_successful

Returns true if the job status indicates successful completion.

    if (Whostmgr::CometBackup::Constants::is_job_successful($status)) {
        # Job completed successfully
    }

=cut

sub is_job_successful {
    my ($status) = @_;
    return ( $status >= JOB_STATUS_STOP_SUCCESS__MIN && $status <= JOB_STATUS_STOP_SUCCESS__MAX );
}

=head2 is_job_failed

Returns true if the job status indicates failure.

    if (Whostmgr::CometBackup::Constants::is_job_failed($status)) {
        # Job failed
    }

=cut

sub is_job_failed {
    my ($status) = @_;
    return ( $status >= JOB_STATUS_FAILED__MIN && $status <= JOB_STATUS_FAILED__MAX );
}

=head2 is_job_completed_with_warning

Returns true if the job completed with warnings (status 7001).
Warnings are common in restore operations (e.g., symlinks already exist)
and should often be treated as successful completion.

    if (Whostmgr::CometBackup::Constants::is_job_completed_with_warning($status)) {
        # Job completed but with warnings
    }

=cut

sub is_job_completed_with_warning {
    my ($status) = @_;
    return ( $status == JOB_STATUS_FAILED_WARNING );
}

=head2 get_job_status_name

Returns a human-readable name for a job status code.

    my $name = Whostmgr::CometBackup::Constants::get_job_status_name($status);
    # Returns: "RUNNING_ACTIVE", "STOP_SUCCESS", "FAILED_ERROR", etc.

=cut

sub get_job_status_name {
    my ($status) = @_;

    my %status_names = (
        JOB_STATUS_STOP_SUCCESS()              => 'STOP_SUCCESS',
        JOB_STATUS_RUNNING_INDETERMINATE()     => 'RUNNING_INDETERMINATE',
        JOB_STATUS_RUNNING_ACTIVE()            => 'RUNNING_ACTIVE',
        JOB_STATUS_RUNNING_REVIVED()           => 'RUNNING_REVIVED',
        JOB_STATUS_RUNNING_TRYAGAIN()          => 'RUNNING_TRYAGAIN',
        JOB_STATUS_NOT_YET_STARTED()           => 'NOT_YET_STARTED',
        JOB_STATUS_FAILED_TIMEOUT()            => 'FAILED_TIMEOUT',
        JOB_STATUS_FAILED_WARNING()            => 'FAILED_WARNING',
        JOB_STATUS_FAILED_ERROR()              => 'FAILED_ERROR',
        JOB_STATUS_FAILED_QUOTA()              => 'FAILED_QUOTA',
        JOB_STATUS_FAILED_SCHEDULEMISSED()     => 'FAILED_SCHEDULEMISSED',
        JOB_STATUS_FAILED_CANCELLED()          => 'FAILED_CANCELLED',
        JOB_STATUS_FAILED_SKIPALREADYRUNNING() => 'FAILED_SKIPALREADYRUNNING',
        JOB_STATUS_FAILED_ABANDONED()          => 'FAILED_ABANDONED',
    );

    return $status_names{$status} // "UNKNOWN($status)";
}

=head2 is_job_restore_classification

Returns true if the classification indicates a restore job.

    if (Whostmgr::CometBackup::Constants::is_job_restore_classification($classification)) {
        # This is a restore job
    }

=cut

sub is_job_restore_classification {
    my ($classification) = @_;
    return ( $classification == JOB_CLASSIFICATION_RESTORE || $classification == JOB_CLASSIFICATION_RESTORE_CUSTOM );
}

=head1 FUNCTIONS

=head2 get_cancel_file_path

Returns the cancel file path for a specific job UUID.

    my $cancel_file = Whostmgr::CometBackup::Constants::get_cancel_file_path($job_uuid);

=cut

sub get_cancel_file_path {
    my ($job_uuid) = @_;
    return sprintf( $CANCEL_FILE_PATTERN, $job_uuid );
}

=head2 get_text_log_file_path

Returns the text log file path for a specific backup run identified by job_uuid and run_uuid.

    my $log_file = Whostmgr::CometBackup::Constants::get_text_log_file_path($job_uuid, $run_uuid);

=cut

sub get_text_log_file_path {
    my ( $job_uuid, $run_uuid ) = @_;
    return sprintf( '%s/%s_%s.log', $TEXT_LOG_DIR, $job_uuid, $run_uuid );
}

=head2 try_acquire_pkgacct_lock

Attempts to acquire a global lock for running pkgacct operations for a specific system user.
Returns 1 if lock was acquired, 0 if another process already holds the lock.

This prevents multiple pkgacct operations from running simultaneously for the same user
across different backup processes.

    if (Whostmgr::CometBackup::Constants::try_acquire_pkgacct_lock($system_user, $item_uid)) {
        # Lock acquired, safe to run pkgacct
    }

=cut

sub try_acquire_pkgacct_lock {
    my ( $system_user, $item_uid ) = @_;

    require Cpanel::Mkdir;
    require Fcntl;

    # Ensure lock directory exists
    unless ( -d $PKGACCT_LOCK_DIR ) {
        Cpanel::Mkdir::ensure_directory_existence_and_mode( $PKGACCT_LOCK_DIR, 0755 );
    }

    my $lock_file = "$PKGACCT_LOCK_DIR/$system_user.lock";

    # Try to create lock file with O_CREAT | O_EXCL (atomic create-if-not-exists)
    if ( sysopen( my $fh, $lock_file, Fcntl::O_CREAT() | Fcntl::O_EXCL() | Fcntl::O_WRONLY(), 0644 ) ) {

        # Successfully created lock file - we have the lock
        print $fh "$$:$item_uid\n";
        close $fh;
        return 1;
    }

    # Lock file already exists - another process has the lock
    return 0;
}

=head2 release_pkgacct_lock

Releases the global pkgacct lock for a specific system user.

    Whostmgr::CometBackup::Constants::release_pkgacct_lock($system_user);

=cut

sub release_pkgacct_lock {
    my ($system_user) = @_;

    my $lock_file = "$PKGACCT_LOCK_DIR/$system_user.lock";

    if ( -f $lock_file ) {
        unlink $lock_file or warn "Failed to remove pkgacct lock file $lock_file: $!";
    }

    return 1;
}

1;
