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

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

package t::medium::Cpanel::Sitejet::MoveWebsite;

use cPstrict;

use Test2::V0 -no_utf8 => 1;
use Test2::Tools::Explain;
use Test2::Plugin::NoWarnings;
use Test2::Tools::Exception qw/dies/;

use Test::MockFile;
use Test::MockObject;
use Test::MockTime;
use Test::Warn;

use Try::Tiny;

use Test::Cpanel::Policy;

# must go below T:C:Policy
use Mock::Cpanel::Logger;

use parent qw(
  Cpanel::TestObj
);

use Cpanel();

# Create a stub Cpanel::Autodie module for testing
{

    package Cpanel::Autodie;
    sub unlink_if_exists { return 1; }
}

use Cpanel::Sitejet::MoveWebsite ();

__PACKAGE__->new()->runtests() if !caller;

#----------------------------------------------------------------------

#e.g., num_method_tests()
sub new ( $class, @args ) {
    my $self = $class->SUPER::new(@args);
    return $self;
}

#e.g., set environs before running tests
sub runtests ( $self, @args ) {
    $self->{'mock_cpuser_file_DNE'}   = Test::MockFile->file('/var/cpanel/users/user_name');
    $self->{'mock_cpuser_file_DNE_2'} = Test::MockFile->file('/var/cpanel/users/rubberduck');
    $self->{'mock_log_file'}          = Test::MockFile->file( '/usr/local/cpanel/logs/error_log',     'fake content' );
    $self->{'mock_cache_file'}        = Test::MockFile->file( '/var/cpanel/userdata/test_user/cache', 'fake content' );
    return $self->SUPER::runtests(@args);
}

sub test_validate_if_sitejet_metadata_exist : Tests(4) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};

    # Mock the memento files to not exist (for normal operation)
    my $memento_file1 = Test::MockFile->file("$mocks->{'homedir'}/tmp/sitejet_migration_rubberduck.tld.failed.yaml");
    my $memento_file2 = Test::MockFile->file("$mocks->{'homedir'}/tmp/sitejet_migration_bathtub.tld.failed.yaml");
    my $memento_file3 = Test::MockFile->file("$mocks->{'homedir'}/tmp/sitejet_migration_bubblebath.tld.failed.yaml");

    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config' => sub { return; },
        )
    );

    my $mov_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );
    ok( $mov_obj->isa('Cpanel::Sitejet::MoveWebsite'), 'Object created successfully' ) or diag explain $mov_obj;
    is( $mov_obj->validate_if_sitejet_metadata_exist(), 1, 'validate_if_sitejet_metadata_exist() returns true' );

    my $empty_obj  = Cpanel::Sitejet::MoveWebsite->new( 'bathtub.tld',    'bathtub.tld' );
    my $double_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bubblebath.tld' );

    is( $empty_obj->validate_if_sitejet_metadata_exist(),  0, 'validate_if_sitejet_metadata_exist() returns false' );
    is( $double_obj->validate_if_sitejet_metadata_exist(), 0, 'validate_if_sitejet_metadata_exist() returns false' );

    return;
}

sub test_move_manifest_data : Tests(4) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};
    my ( $source_file, $dest_file );
    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config' => sub { return; },
            'Cpanel::FileUtils::Move::safemv'                  => sub {
                ( $source_file, $dest_file ) = @_;
                return 1;
            },
        )
    );

    my $mov_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );
    ok( $mov_obj->isa('Cpanel::Sitejet::MoveWebsite'), 'Object created successfully' ) or diag explain $mov_obj;

    is( $mov_obj->move_manifest_data(), 1,                                             'move_manifest_data() returns true' );
    is( $source_file,                   "$mocks->{'sitejetdir'}/rubberduck.tld-files", 'Source file path is correct' );
    is( $dest_file,                     "$mocks->{'sitejetdir'}/bathtub.tld-files",    'Destination file path is correct' );

    return;
}

sub test_move_metadata : Tests(6) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};
    my ( $source_file, $dest_file, $update_domain, $update_metadata );
    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config' => sub { return; },
            'Cpanel::FileUtils::Move::safemv'                  => sub {
                ( $source_file, $dest_file ) = @_;
                return 1;
            },
            'Cpanel::Sitejet::Connector::cp_update_sitejet_metadata' => sub {
                shift;
                ( $update_domain, $update_metadata ) = @_;
                return 1;
            },
        )
    );

    my $mov_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );
    ok( $mov_obj->isa('Cpanel::Sitejet::MoveWebsite'), 'Object created successfully' ) or diag explain $mov_obj;

    my $expected_metadata = {
        'domain'           => 'bathtub.tld',
        'company'          => 'bathtub.tld',
        'cpanelDomainGUID' => 'bathtub.tld',
    };

    is( $mov_obj->move_metadata(), 1,                                       'move_metadata() returns true' );
    is( $source_file,              "$mocks->{'sitejetdir'}/rubberduck.tld", 'Source file path is correct' );
    is( $dest_file,                "$mocks->{'sitejetdir'}/bathtub.tld",    'Destination file path is correct' );
    is( $update_domain,            'bathtub.tld',                           'update_new_metadata was called with correct domain' );
    is( $update_metadata,          $expected_metadata,                      'update_new_metadata was called with correct metadata' );

    return;
}

sub test_update_new_metadata : Tests(4) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};
    my ( $domain, $metadata );
    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config'       => sub { return; },
            'Cpanel::Sitejet::Connector::cp_update_sitejet_metadata' => sub {
                shift;
                ( $domain, $metadata ) = @_;
                return 1;
            },
        )
    );

    my $mov_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );
    ok( $mov_obj->isa('Cpanel::Sitejet::MoveWebsite'), 'Object created successfully' ) or diag explain $mov_obj;

    my $expected_metadata = {
        'domain'           => 'bathtub.tld',
        'company'          => 'bathtub.tld',
        'cpanelDomainGUID' => 'bathtub.tld',
    };

    is( $mov_obj->update_new_metadata(), 1,                  'update_new_metadata() returns true' );
    is( $domain,                         'bathtub.tld',      'Domain is updated correctly' );
    is( $metadata,                       $expected_metadata, 'Metadata is updated correctly' );

    return;
}

sub test_replace_old_domain_in_files : Tests(6) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};
    *Cpanel::Autodie::open = *CORE::GLOBAL::open;
    my @overwritten_files;
    my @overwrite_data;
    my @perms;
    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config' => sub { return; },
            'Cpanel::Sitejet::Connector::list_manifest_files'  => sub {
                my @lines = split /\n/, $mocks->{'mock_manifest'}->contents();
                return @lines;
            },
            'File::Find::Rule::in' => sub {
                my $self       = shift;
                my @lines      = @_;
                my @mock_lines = split /\n/, $mocks->{'mock_manifest'}->contents();
                my $fragment   = $self->_compile;
                my %subs       = %{ $self->{subs} };

                foreach my $line (@lines) {
                    foreach my $sub ( keys %subs ) {
                        $subs{$sub}->( $self, { wanted => $sub }, $line );
                    }
                }
                return @lines;
            },
            'Cpanel::FileUtils::Write::overwrite' => sub {
                my ( $file, $data, $perms ) = @_;

                push @overwritten_files, $file;
                push @overwrite_data,    $data;
                push @perms,             $perms;
                return 1;
            }
        )
    );

    my $mov_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );
    ok( $mov_obj->isa('Cpanel::Sitejet::MoveWebsite'), 'Object created successfully' ) or diag explain $mov_obj;

    is( $mov_obj->replace_old_domain_in_files(), 1, 'replace_old_domain_in_files() returns true' );

    is( @overwritten_files,    1,                             'One file was overwritten' );
    is( $overwritten_files[0], $mocks->{'text_file'}->path(), 'Overwritten file contains old domain' );
    is( $overwrite_data[0],    'domain: bathtub.tld',         'Overwritten data is correct' );
    is( $perms[0],             0644,                          'Permissions are set to 0644' );

    return;
}

sub test_run : Tests(11) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};

    # Mock the memento files to not exist (for normal operation)
    my $memento_file1 = Test::MockFile->file("$mocks->{'homedir'}/tmp/sitejet_migration_rubberduck.tld.failed.yaml");
    my $memento_file2 = Test::MockFile->file("$mocks->{'homedir'}/tmp/sitejet_migration_bathtub.tld.failed.yaml");

    my ( $domain, $metadata );
    my @source_files;
    my @dest_files;
    my @overwritten_files;
    my @overwrite_data;
    my @perms;
    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config'       => sub { return; },
            'Cpanel::Sitejet::Connector::cp_update_sitejet_metadata' => sub {
                shift;
                ( $domain, $metadata ) = @_;
                return 1;
            },

            'Cpanel::FileUtils::Move::safemv' => sub {
                my ( $src, $dest ) = @_;
                push @source_files, $src;
                push @dest_files,   $dest;
                return 1;
            },
            'Cpanel::Sitejet::Connector::list_manifest_files' => sub {
                my @lines = split /\n/, $mocks->{'mock_manifest'}->contents();
                return @lines;
            },
            'File::Find::Rule::in' => sub {
                my $self       = shift;
                my @lines      = @_;
                my @mock_lines = split /\n/, $mocks->{'mock_manifest'}->contents();
                my $fragment   = $self->_compile;
                my %subs       = %{ $self->{subs} };

                foreach my $line (@lines) {
                    foreach my $sub ( keys %subs ) {
                        $subs{$sub}->( $self, { wanted => $sub }, $line );
                    }
                }
                return @lines;
            },
            'Cpanel::FileUtils::Write::overwrite' => sub {
                my ( $file, $data, $perms ) = @_;

                push @overwritten_files, $file;
                push @overwrite_data,    $data;
                push @perms,             $perms;
                return 1;
            }
        )
    );

    my $mov_obj  = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );
    my $fail_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bubblebath.tld' );
    ok( $mov_obj->isa('Cpanel::Sitejet::MoveWebsite'), 'Object created successfully' ) or diag explain $mov_obj;

    is( $fail_obj->run(),      -1,                                                                                      'run() returns false for invalid object' );
    is( $mov_obj->run(),        1,                                                                                      'run() returns true' );
    is( @source_files,          2,                                                                                      'Two source files were moved' ) or diag explain \@source_files;
    is( \@source_files,        [ $mocks->{'file_mock'}->path(), $mocks->{'mock_manifest'}->path() ],                    'Source files are correct' );
    is( @dest_files,           2,                                                                                       'Two destination files were moved' ) or diag explain \@dest_files;
    is( \@dest_files,          [ $mocks->{'file_mock_empty'}->path(), $mocks->{'file_mock_empty'}->path() . "-files" ], 'Destination files are correct' );
    is( @overwritten_files,    1,                                                                                       'One file was overwritten' );
    is( $overwritten_files[0], $mocks->{'text_file'}->path(),                                                           'Overwritten file contains old domain' );
    is( $overwrite_data[0],    'domain: bathtub.tld',                                                                   'Overwritten data is correct' );
    is( $perms[0],             0644,                                                                                    'Permissions are set to 0644' );

    return;
}

sub test_run_with_errors : Tests(8) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};

    # Mock the memento files to not exist (for normal operation)
    my $memento_file1 = Test::MockFile->file("$mocks->{'homedir'}/tmp/sitejet_migration_rubberduck.tld.failed.yaml");
    my $memento_file2 = Test::MockFile->file("$mocks->{'homedir'}/tmp/sitejet_migration_bathtub.tld.failed.yaml");

    my ( $domain, $metadata );
    my @source_files;
    my @dest_files;
    my @overwritten_files;
    my @overwrite_data;
    my @perms;
    my $move_manifest_called   = 0;
    my $move_metadata_called   = 0;
    my $update_metadata_called = 0;
    my $replace_files_called   = 0;

    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config'       => sub { return; },
            'Cpanel::Sitejet::Connector::cp_update_sitejet_metadata' => sub {
                shift;
                ( $domain, $metadata ) = @_;
                $update_metadata_called = 1;
                return 1;
            },

            'Cpanel::FileUtils::Move::safemv' => sub {
                my ( $src, $dest ) = @_;
                push @source_files, $src;
                push @dest_files,   $dest;

                # Simulate failure for manifest move but success for metadata move
                # it continues if it returned failure but does not continue when it dies
                if ( $src =~ /-files$/ ) {
                    $move_manifest_called = 1;
                    return 0;
                }
                else {
                    $move_metadata_called = 1;
                    return 1;
                }
            },
            'Cpanel::Sitejet::Connector::list_manifest_files' => sub {
                my @lines = split /\n/, $mocks->{'mock_manifest'}->contents();
                $replace_files_called = 1;
                return @lines;
            },
            'File::Find::Rule::in' => sub {
                my $self       = shift;
                my @lines      = @_;
                my @mock_lines = split /\n/, $mocks->{'mock_manifest'}->contents();
                my $fragment   = $self->_compile;
                my %subs       = %{ $self->{subs} };

                foreach my $line (@lines) {
                    foreach my $sub ( keys %subs ) {
                        $subs{$sub}->( $self, { wanted => $sub }, $line );
                    }
                }
                return @lines;
            },
            'Cpanel::FileUtils::Write::overwrite' => sub {
                my ( $file, $data, $perms ) = @_;

                push @overwritten_files, $file;
                push @overwrite_data,    $data;
                push @perms,             $perms;
                return 1;
            }
        )
    );

    my $mov_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );
    ok( $mov_obj->isa('Cpanel::Sitejet::MoveWebsite'), 'Object created successfully' ) or diag explain $mov_obj;

    # With the new behavior, run() should return immediately on any failures
    is( $mov_obj->run(), 0, 'run() returns false when some operations fail' );

    # Verify all operations were attempted despite the manifest move failure
    is( $move_metadata_called,   1, 'move_metadata was called since it is the first operation.' );
    is( $update_metadata_called, 1, 'update_new_metadata was called since move_metadata was successful.' );
    is( $move_manifest_called,   1, 'move_manifest_data was called move_metadata was successful.' );
    is( $replace_files_called,   0, 'replace_old_domain_in_files should not have been called despite since move_manifest_data failed.' );

    # Verify that successful operations still completed
    is( @source_files,      2, 'Both file operations were attempted' );
    is( @overwritten_files, 0, 'File replacement should not occur as move_manifest failed' );

    return;
}

sub test_clean_up_failure_memento_success : Tests(2) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};

    # Mock the memento file to exist
    my $memento_file = "$mocks->{'homedir'}/tmp/sitejet_migration_rubberduck.tld.failed.yaml";
    my $memento_mock = Test::MockFile->file( $memento_file, 'test memento content' );

    my $unlink_called    = 0;
    my $unlink_file_path = '';

    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config' => sub { return; },
            'Cpanel::Autodie::unlink_if_exists'                => sub {
                my ($file) = @_;
                $unlink_called    = 1;
                $unlink_file_path = $file;
                return 1;
            },
        )
    );

    my $mov_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );
    $mov_obj->_clean_up_failure_memento();

    is( $unlink_called,    1,             '_clean_up_failure_memento calls unlink_if_exists' );
    is( $unlink_file_path, $memento_file, 'unlink_if_exists called with correct file path' );

    return;
}

sub test_clean_up_failure_memento_with_error : Tests(1) ($self) {
    my $mocks = $self->_get_mocks();
    local $Cpanel::user    = $mocks->{'user'};
    local $Cpanel::homedir = $mocks->{'homedir'};

    # Mock the memento file to exist
    my $memento_file = "$mocks->{'homedir'}/tmp/sitejet_migration_rubberduck.tld.failed.yaml";
    my $memento_mock = Test::MockFile->file( $memento_file, 'test memento content' );

    my $error_logged = 0;

    my @mocks = (
        $self->mock_subs(
            'Cpanel::Sitejet::Connector::_check_domain_config' => sub { return; },
            'Cpanel::Autodie::unlink_if_exists'                => sub {
                die "Permission denied";
            },
            'Cpanel::Sitejet::MoveWebsite::logger' => sub {
                return bless {}, 'MockLogger';
            },
        )
    );

    # Mock logger to capture error messages
    {
        no warnings 'once', 'redefine';
        *MockLogger::error = sub {
            my ( $self, $msg ) = @_;
            $error_logged = 1 if $msg =~ /Failed to remove memento file/;
        };
    }

    my $mov_obj = Cpanel::Sitejet::MoveWebsite->new( 'rubberduck.tld', 'bathtub.tld' );

    # This should not die even if unlink fails
    eval { $mov_obj->_clean_up_failure_memento(); };

    is( $error_logged, 1, '_clean_up_failure_memento logs error when unlink fails but does not die' );

    return;
}

sub _get_mocks ($self) {
    my %mocks        = ();
    my $html_content = qq(<div class="ed-element ed-container banner preset-banner-v3-default");
    $mocks{'user'}            = 'rubberduck';
    $mocks{'domain'}          = 'rubberduck.tld';
    $mocks{'homedir'}         = '/home/rubberduck';
    $mocks{'sitejetdir'}      = "$mocks{'homedir'}/.cpanel/sitejet";
    $mocks{'dir_mock'}        = Test::MockFile->new_dir( $mocks{'sitejetdir'} );
    $mocks{'public_html'}     = "$mocks{'homedir'}/public_html";
    $mocks{'html_mockdir'}    = Test::MockFile->new_dir( $mocks{'public_html'} );
    $mocks{'mock_manifest'}   = Test::MockFile->file( "$mocks{'sitejetdir'}/rubberduck.tld-files", "$mocks{'public_html'}/index.html\n$mocks{'public_html'}/random.txt\n" );
    $mocks{'index_file_mock'} = Test::MockFile->file( "$mocks{'public_html'}/index.html",          $html_content );
    $mocks{'text_file'}       = Test::MockFile->file( "$mocks{'public_html'}/random.txt",          'domain: rubberduck.tld' );
    $mocks{'content'}         = '{"cpanelDomainGUID":"rubberduck.tld","websiteId":391160,"domain":"rubberduck.tld","fullcms":1,"publish_status":1,"language":"en","document_root":"/home/rubberduck/public_html","company":"rubberduck.tld"}';
    $mocks{'file_mock'}       = Test::MockFile->file( "$mocks{sitejetdir}/rubberduck.tld", $mocks{'content'} );
    $mocks{'file_mock_empty'} = Test::MockFile->file( "$mocks{sitejetdir}/bathtub.tld",    '{}' );
    $mocks{'file_mock_copy'}  = Test::MockFile->file( "$mocks{sitejetdir}/bubblebath.tld", $mocks{'content'} );
    return \%mocks;
}

1;
