package Whostmgr::CometBackup::CometBackupAPI::ProtectedItemRestore;

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

# This module handles restoration of Protected Items from Comet servers

use cPstrict;

use Cpanel::JSON                  ();
use Whostmgr::CometBackup::Logger ();

=head1 NAME

Whostmgr::CometBackup::CometBackupAPI::ProtectedItemRestore - Protected Item restoration for Comet servers

=head1 SYNOPSIS

    use Whostmgr::CometBackup::CometBackupAPI::ProtectedItemRestore;
    
    # Create a restore manager for a specific device
    my $restore = Whostmgr::CometBackup::CometBackupAPI::ProtectedItemRestore->new(
        target_device => 'device-guid-12345'
    );
    
    # Restore a Protected Item to its original location
    my $success = $restore->restore_protected_item(
        source_id      => 'protected-item-uuid',
        destination_id => 'storage-vault-uuid',
        snapshot_id    => 'snapshot-id-12345',  # Optional, uses latest if omitted
    );
    
    # Restore to a custom location
    my $success = $restore->restore_to_custom_location(
        source_id      => 'protected-item-uuid',
        destination_id => 'storage-vault-uuid',
        snapshot_id    => 'snapshot-id-12345',
        restore_path   => '/home/restored-data',
        options        => {
            overwrite_existing => 1,
            restore_permissions => 1,
        }
    );
    
    # Restore specific paths only
    my $success = $restore->restore_specific_paths(
        source_id      => 'protected-item-uuid',
        destination_id => 'storage-vault-uuid',
        snapshot_id    => 'snapshot-id-12345',
        paths          => [
            '/home/tau/important.txt',
            '/home/tau/documents/',
        ],
        restore_path   => '/restore-target',
    );

=head1 DESCRIPTION

This module provides Protected Item restoration capabilities for Comet servers,
designed to restore backup data using the UserWebDispatcherRunRestoreCustom API.

=head2 new

    my $restore = Whostmgr::CometBackup::CometBackupAPI::ProtectedItemRestore->new(
        target_device => 'device-guid-12345'
    );

Creates a new Protected Item restore manager for a specific Comet device.

Parameters:
- target_device: The live connection GUID of the target device (required)

=cut

sub new ( $class, %args ) {
    my $target_device = $args{target_device} or die "target_device parameter required";

    return bless {
        target_device => $target_device,
        _logger       => Whostmgr::CometBackup::Logger->new( log_level => $ENV{DEBUG} ? 'debug' : 'info' ),
    }, $class;
}

=head2 restore_protected_item

    my $job_id = $restore->restore_protected_item(
        source_id      => 'protected-item-uuid',
        destination_id => 'storage-vault-uuid',
        snapshot_id    => 'snapshot-id-12345',  # Optional
        options        => { ... }                # Optional
    );

Restores a Protected Item using the default restore location (original location).
This uses the Comet UserWebDispatcherRunRestoreCustom API.

Parameters:
- source_id: The Protected Item GUID (required)
- destination_id: The Storage Vault GUID (required)
- snapshot_id: Specific snapshot to restore; uses latest if omitted (optional)
- options: Hash reference with restore options (optional)

Returns: Job ID string on success, 0 on failure

=cut

sub restore_protected_item ( $self, %args ) {
    my $source_id      = $args{source_id}      or die "source_id parameter required";
    my $destination_id = $args{destination_id} or die "destination_id parameter required";
    my $snapshot_id    = $args{snapshot_id};
    my $options        = $args{options} || {};
    my $username       = $args{username};        # Username for process-type restores

    my $logger = $self->{_logger};

    $logger->debug(
        "Initiating Protected Item restore",
        {
            target_device  => $self->{target_device},
            source_id      => $source_id,
            destination_id => $destination_id,
            snapshot_id    => $snapshot_id || 'latest',
            username       => $username    || '',
        }
    );

    # Fetch source details to determine restore type
    require Whostmgr::CometBackup::CometBackupAPI;
    my $profile_result = Whostmgr::CometBackup::CometBackupAPI::make_server_request("user/web/get-user-profile-and-hash");

    unless ( $profile_result && $profile_result->{Status} == 200 && $profile_result->{Profile} ) {
        die "Failed to retrieve user profile from Comet server";
    }

    my $profile     = $profile_result->{Profile};
    my $source_item = $profile->{Sources}->{$source_id};

    unless ($source_item) {
        die "Source $source_id not found in user profile";
    }

    my $engine_type = $source_item->{Engine} || '';
    $logger->debug("Source engine type: $engine_type");

    # Build restore options - always provide defaults if not specified
    my $restore_options = $self->_build_restore_options($options);

    # Determine restore type based on engine
    my $restore_type;
    my $dest_path = "";

    # Look up the "item_type" for the requested Protected Item + snapshot_id from the item_runs table
    # This determines the restore type and $dest_path logic
    require Whostmgr::CometBackup::BackupRunLogs;
    my $runlogs = Whostmgr::CometBackup::BackupRunLogs->new();
    my $item_type;
    if ( $source_id && $snapshot_id ) {
        my @items = $runlogs->get_restorable_items(
            system_user => $username,
            limit       => 10
        );
        foreach my $item (@items) {
            if ( $item->{protected_item_uid} eq $source_id && $item->{comet_snapshot_id} eq $snapshot_id ) {
                $item_type = $item->{protected_item_type};
                last;
            }
        }
    }

    # Fallback: try to find by source_id only if snapshot_id not found
    if ( !$item_type && $source_id ) {
        my @items = $runlogs->get_restorable_items(
            system_user => $username,
            limit       => 10
        );
        foreach my $item (@items) {
            if ( $item->{protected_item_uid} eq $source_id ) {
                $item_type = $item->{protected_item_type};
                last;
            }
        }
    }
    $logger->debug( "Detected item_type for restore: " . ( $item_type // 'unknown' ) );

    # Set $dest_path based on item_type
    if ( $item_type && $item_type eq 'pkgacct_program_output' ) {

        # panel_config backup: use restorepkg script
        # Disable Suspend/Unsuspend to prevent restoring old suspension state
        # Check if account exists to determine whether to use --skipaccount flag
        my $account_exists = 0;
        if ($username) {

            # Check if user exists on the system
            $account_exists = getpwnam($username) ? 1 : 0;
        }

        if ($account_exists) {

            # Account exists - use --skipaccount to update existing account without recreating it
            $dest_path = " /usr/local/cpanel/scripts/restorepkg --disable=Homedir,Mysql,MysqlRemoteNotes,Suspend,Unsuspend --from-stdin --skipaccount --newuser $username";
            $logger->debug("Account $username exists - using --skipaccount flag");
        }
        else {
            # Account doesn't exist - omit --skipaccount so restorepkg creates the account
            $dest_path = " /usr/local/cpanel/scripts/restorepkg --disable=Homedir,Mysql,MysqlRemoteNotes,Suspend,Unsuspend --from-stdin --newuser $username";
            $logger->debug("Account $username does not exist - creating new account");
        }

        $restore_type = 2;
        $logger->debug("Using process restore type (Type=2) for pkgacct_program_output with DestPath: $dest_path");
    }
    elsif ( $item_type && $item_type eq 'database_program_output' ) {

        # database backup: use restorepkg script
        # Note: Suspend and Unsuspend already in disable list
        $dest_path =
          " /usr/local/cpanel/scripts/restorepkg --disable=Homedir,Roundcube,APITokens,AuthnLinks,AutoDomains,BackupConfig,BandwidthData,BrandCustomizations,CpUser,Cron,CustomLocale,DigestShadow,Domains,FeatureList,FileProtect,FlattenDocroot,Ftp,Htaccess,IPAddress,Integration,LinkedNodes,LinkedNodesSubarchives,LocalConfig,Logs,Mail,MailFix,MailLimits,MailSync,Mailman,ManualMX,NativeDAV,NobodyFiles,OldHomedirs,Package,Password,PostRestoreActions,PreRestoreActions,PublicContact,PublicHtmlSymlinks,Quota,Reseller,ResyncHomedir,SSL,ServiceProxy,Shell,SpamAssassin,Subaccount,Suspend,Team,Unsuspend,VhostIncludes,Vhosts,WebCalls,WebDiskHomedir,userdata --from-stdin --skipaccount --newuser $username";
        $restore_type = 2;
        $logger->debug("Using process restore type (Type=2) for database_program_output with DestPath: $dest_path");
    }
    elsif ( $item_type && $item_type eq 'homedir_files_folders' ) {

        # homedir backup: native file restore
        $dest_path    = "/home/";    # username is built into the base directory of the backup, so a dest_path of /home/user/ would result in /home/user/user/
        $restore_type = 0;
        $logger->debug("Using file restore type (Type=0) for homedir_files_folders");
    }
    else {
        # Fallback to engine_type logic if item_type is unknown
        if ( $engine_type eq 'engine1/stdout' || $engine_type =~ /stdout/i ) {
            $restore_type = 2;
            if ($username) {

                # Disable Suspend/Unsuspend to prevent restoring old suspension state
                $dest_path = " /usr/local/cpanel/scripts/restorepkg --disable=Suspend,Unsuspend --from-stdin --skipaccount --newuser $username";
            }
            $logger->debug("Using process restore type (Type=2) with DestPath: $dest_path (fallback)");
        }
        else {
            $restore_type = 0;
            $dest_path    = "";
            $logger->debug("Using file restore type (Type=0) (fallback)");
        }
    }

    # Override restore options with correct type and dest path
    $restore_options = {
        Type     => $restore_type,
        DestPath => $dest_path,
    };

    if ( $restore_type == 0 ) {

        # For file-based restores, ensure we overwrite existing files and restore to their original location
        $restore_options->{OverwriteExistingFiles} = Cpanel::JSON::true;
        $restore_options->{DestIsOriginalLocation} = Cpanel::JSON::true;
        $restore_options->{DestPath}               = "";
    }

    # If no options were provided, use required defaults
    # if ( !keys %$restore_options ) {
    #     require Cpanel::JSON;
    #     $restore_options = {
    #         Type                       => 0,                    # RESTORETYPE_FILE
    #         OverwriteExistingFiles     => Cpanel::JSON::true,   # Allow overwriting existing files
    #         OverwriteIfDifferentContent => Cpanel::JSON::true,  # Overwrite if content differs
    #         DestIsOriginalLocation     => Cpanel::JSON::true,   # Restore to original location
    #         DestPath                   => ""                    # Empty since DestIsOriginalLocation is true
    #     };
    #     $logger->debug("No restore options configured, using defaults");
    # }

    # Execute restore via Comet API
    require Cpanel::JSON;

    # Build request parameters matching Comet UI format
    my %request_params = (
        TargetID       => $self->{target_device},
        Source         => $source_id,
        Destination    => $destination_id,
        Paths          => "[]",                                   # Empty JSON array string
        Options        => Cpanel::JSON::Dump($restore_options),
        KnownFileCount => "-1",
        KnownByteCount => "-1",
        KnownDirCount  => "-1",
    );

    $logger->debug( "Restore options JSON: " . $request_params{Options} );

    # Add Snapshot if specified
    if ($snapshot_id) {
        $request_params{Snapshot} = $snapshot_id;
    }

    # Log the full request before sending
    $logger->debug(
        "Request parameters for run-restore-custom",
        {
            params      => \%request_params,
            params_json => Cpanel::JSON::Dump( \%request_params ),
        }
    );

    my $result = Whostmgr::CometBackup::CometBackupAPI::make_server_request(
        "user/web/dispatcher/run-restore-custom",
        \%request_params
    );

    if ( $result && $result->{Status} == 200 ) {

        # Extract job ID from response
        # The user/web/dispatcher/run-restore-custom API now returns a JobID
        my $job_id = $result->{JobID};

        $logger->debug(
            "Successfully initiated Protected Item restore",
            {
                target_device => $self->{target_device},
                source_id     => $source_id,
                job_id        => $job_id // 'unknown',
            }
        );

        # Return job ID from the response
        return $job_id;
    }

    $logger->error(
        "Failed to initiate Protected Item restore",
        {
            target_device => $self->{target_device},
            source_id     => $source_id,
            status        => $result->{Status}  // 'unknown',
            message       => $result->{Message} // 'unknown error',
        }
    );

    return 0;
}

=head2 restore_to_custom_location

    my $success = $restore->restore_to_custom_location(
        source_id      => 'protected-item-uuid',
        destination_id => 'storage-vault-uuid',
        restore_path   => '/home/restored-data',
        snapshot_id    => 'snapshot-id-12345',  # Optional
        options        => { ... }                # Optional
    );

Restores a Protected Item to a custom location instead of its original location.

Parameters:
- source_id: The Protected Item GUID (required)
- destination_id: The Storage Vault GUID (required)
- restore_path: The local path to restore to (required)
- snapshot_id: Specific snapshot to restore; uses latest if omitted (optional)
- options: Hash reference with restore options (optional)

Returns: 1 on success, 0 on failure

=cut

sub restore_to_custom_location ( $self, %args ) {
    my $source_id      = $args{source_id}      or die "source_id parameter required";
    my $destination_id = $args{destination_id} or die "destination_id parameter required";
    my $restore_path   = $args{restore_path}   or die "restore_path parameter required";
    my $snapshot_id    = $args{snapshot_id};
    my $options        = $args{options} || {};

    my $logger = $self->{_logger};

    $logger->debug(
        "Initiating restore to custom location",
        {
            target_device  => $self->{target_device},
            source_id      => $source_id,
            destination_id => $destination_id,
            restore_path   => $restore_path,
            snapshot_id    => $snapshot_id || 'latest',
        }
    );

    # Build restore options with custom destination
    my $restore_options = $self->_build_restore_options(
        {
            %$options,
            restore_to_path => $restore_path,
        }
    );

    # If no options were provided, use required defaults (but with custom path)
    if ( !keys %$restore_options ) {
        require Cpanel::JSON;
        $restore_options = {
            Type                        => 0,                      # RESTORETYPE_FILE
            OverwriteExistingFiles      => Cpanel::JSON::true,     # Allow overwriting existing files
            OverwriteIfDifferentContent => Cpanel::JSON::true,     # Overwrite if content differs
            DestIsOriginalLocation      => Cpanel::JSON::false,    # Restore to custom location
            DestPath                    => $restore_path           # Custom restore path
        };
        $logger->debug("No restore options configured, using defaults with custom path");
    }

    # Execute restore via Comet API
    require Whostmgr::CometBackup::CometBackupAPI;
    require Cpanel::JSON;

    # Build request parameters - Options is now always included
    # Match the format used by working comet-manager.pl code
    my %request_params = (
        TargetID    => $self->{target_device},
        Source      => $source_id,
        Destination => $destination_id,
        Paths       => [$restore_path],
        Options     => Cpanel::JSON::Dump($restore_options),
    );

    $logger->debug( "Restore options JSON: " . $request_params{Options} );

    # Add Snapshot if specified
    if ($snapshot_id) {
        $request_params{Snapshot} = $snapshot_id;
    }

    # Log the full request before sending
    $logger->debug(
        "Request parameters for run-restore-custom",
        {
            params      => \%request_params,
            params_json => Cpanel::JSON::Dump( \%request_params ),
        }
    );

    my $result = Whostmgr::CometBackup::CometBackupAPI::make_server_request(
        "user/web/dispatcher/run-restore-custom",
        \%request_params
    );

    if ( $result && $result->{Status} == 200 ) {
        my $job_id = $result->{JobID};
        $logger->debug(
            "Successfully initiated restore to custom location",
            {
                target_device => $self->{target_device},
                source_id     => $source_id,
                restore_path  => $restore_path,
                job_id        => $job_id // 'unknown',
            }
        );
        return 1;
    }

    $logger->error(
        "Failed to initiate restore to custom location",
        {
            target_device => $self->{target_device},
            source_id     => $source_id,
            restore_path  => $restore_path,
            status        => $result->{Status}  // 'unknown',
            message       => $result->{Message} // 'unknown error',
        }
    );

    return 0;
}

=head2 restore_specific_paths

    my $success = $restore->restore_specific_paths(
        source_id      => 'protected-item-uuid',
        destination_id => 'storage-vault-uuid',
        paths          => ['/path1', '/path2'],
        restore_path   => '/restore-target',     # Optional
        snapshot_id    => 'snapshot-id-12345',   # Optional
        options        => { ... }                 # Optional
    );

Restores only specific paths from a Protected Item backup.

Parameters:
- source_id: The Protected Item GUID (required)
- destination_id: The Storage Vault GUID (required)
- paths: Array reference of paths to restore (required)
- restore_path: The local path to restore to; uses original if omitted (optional)
- snapshot_id: Specific snapshot to restore; uses latest if omitted (optional)
- options: Hash reference with restore options (optional)

Returns: 1 on success, 0 on failure

=cut

sub restore_specific_paths ( $self, %args ) {
    my $source_id      = $args{source_id}      or die "source_id parameter required";
    my $destination_id = $args{destination_id} or die "destination_id parameter required";
    my $paths          = $args{paths}          or die "paths parameter required";
    my $restore_path   = $args{restore_path};
    my $snapshot_id    = $args{snapshot_id};
    my $options        = $args{options} || {};

    unless ( ref $paths eq 'ARRAY' && @$paths ) {
        die "paths parameter must be a non-empty array reference";
    }

    my $logger = $self->{_logger};

    $logger->debug(
        "Initiating selective path restore",
        {
            target_device  => $self->{target_device},
            source_id      => $source_id,
            destination_id => $destination_id,
            path_count     => scalar(@$paths),
            restore_path   => $restore_path || 'original',
            snapshot_id    => $snapshot_id  || 'latest',
        }
    );

    # Build restore options
    my $restore_options = $self->_build_restore_options(
        {
            %$options,
            $restore_path ? ( restore_to_path => $restore_path ) : (),
        }
    );

    # If no options were provided, use required defaults
    # if ( !keys %$restore_options ) {
    #     require Cpanel::JSON;
    #     $restore_options = {
    #         Type                       => 0,                        # RESTORETYPE_FILE
    #         OverwriteExistingFiles     => Cpanel::JSON::true,       # Allow overwriting existing files
    #         OverwriteIfDifferentContent => Cpanel::JSON::true,      # Overwrite if content differs
    #         DestIsOriginalLocation     => $restore_path ? Cpanel::JSON::false : Cpanel::JSON::true,
    #         DestPath                   => $restore_path || ""       # Custom or empty
    #     };
    #     $logger->debug("No restore options configured, using defaults");
    # }

    # Execute restore via Comet API
    require Whostmgr::CometBackup::CometBackupAPI;
    require Cpanel::JSON;

    # Build request parameters - Options is now always included
    # Match the format used by working comet-manager.pl code
    my $paths_json     = Cpanel::JSON::Dump($paths);
    my %request_params = (
        TargetID    => $self->{target_device},
        Source      => $source_id,
        Destination => $destination_id,
        Path        => [],                                     # Empty if restoring to original
        Paths       => $paths_json,
        Options     => Cpanel::JSON::Dump($restore_options),
    );

    $logger->debug("Restore paths JSON: $paths_json");
    $logger->debug( "Restore options JSON: " . $request_params{Options} );

    # Add Snapshot if specified
    if ($snapshot_id) {
        $request_params{Snapshot} = $snapshot_id;
    }

    # Log the full request before sending
    $logger->debug(
        "Request parameters for run-restore-custom",
        {
            params      => \%request_params,
            params_json => Cpanel::JSON::Dump( \%request_params ),
        }
    );

    my $result = Whostmgr::CometBackup::CometBackupAPI::make_server_request(
        "user/web/dispatcher/run-restore-custom",
        \%request_params
    );

    if ( $result && $result->{Status} == 200 ) {
        my $job_id = $result->{JobID};
        $logger->debug(
            "Successfully initiated selective path restore",
            {
                target_device => $self->{target_device},
                source_id     => $source_id,
                path_count    => scalar(@$paths),
                job_id        => $job_id // 'unknown',
            }
        );
        return 1;
    }

    $logger->error(
        "Failed to initiate selective path restore",
        {
            target_device => $self->{target_device},
            source_id     => $source_id,
            path_count    => scalar(@$paths),
            status        => $result->{Status}  // 'unknown',
            message       => $result->{Message} // 'unknown error',
        }
    );

    return 0;
}

=head2 get_restore_status

    my $status = $restore->get_restore_status($job_id);

Gets the status of a running or completed restore job.

Parameters:
- job_id: The restore job ID returned by the restore operation

Returns: Hash reference with job status, or undef on failure

=cut

sub get_restore_status ( $self, $job_id ) {
    my $logger = $self->{_logger};

    $logger->debug(
        "Fetching restore job status",
        {
            job_id => $job_id,
        }
    );

    unless ($job_id) {
        $logger->error("job_id parameter required");
        return;
    }

    require Whostmgr::CometBackup::CometBackupAPI;
    my $result = Whostmgr::CometBackup::CometBackupAPI::make_server_request(
        "user/web/get-job-properties",
        {
            JobID => $job_id,
        }
    );

    if ( $result && $result->{Status} == 200 ) {
        return {
            job_id         => $job_id,
            status         => $result->{Status},
            classification => $result->{Classification} || 'Unknown',
            start_time     => $result->{StartTime}      || 0,
            end_time       => $result->{EndTime}        || 0,
            total_size     => $result->{TotalSize}      || 0,
            download_size  => $result->{DownloadSize}   || 0,
            progress       => $result->{Progress}       || 0,
        };
    }

    $logger->error(
        "Failed to get restore status",
        {
            job_id  => $job_id,
            status  => $result->{Status}  // 'unknown',
            message => $result->{Message} // 'unknown error',
        }
    );

    return;
}

=head2 _build_restore_options

    my $options_hashref = $self->_build_restore_options($input_options);

Builds the RestoreJobAdvancedOptions structure for the Comet API.

Parameters:
- $input_options: Hash reference with user-specified restore options

Returns: Hash reference containing Comet API RestoreJobAdvancedOptions fields.
The returned hash should be JSON-encoded before sending to the API.

=cut

sub _build_restore_options ( $self, $options ) {
    my $restore_opts = {};

    # Destination options
    if ( $options->{restore_to_path} ) {
        $restore_opts->{DestPath} = $options->{restore_to_path};
    }

    # Overwrite settings
    if ( defined $options->{overwrite_existing} ) {
        $restore_opts->{OverwriteExistingFiles} = $options->{overwrite_existing} ? \1 : \0;
    }

    # Archive format (for database/application restores)
    if ( $options->{archive_format} ) {
        $restore_opts->{ArchiveFormat} = $options->{archive_format};
    }

    return $restore_opts;
}

1;

=head1 AUTHOR

cPanel, L.L.C.

=head1 COPYRIGHT

Copyright 2025 WebPros International, LLC. All rights reserved.

=cut
