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

# cpanel - bin/wpsmartphpcleanup                   Copyright 2024 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
package bin::wpsmartphpcleanup;

use cPstrict;

use Cpanel::Binaries::WPTK  ();
use Cpanel::JSON            ();
use Cpanel::Logger          ();
use Cpanel::Unix::PID::Tiny ();
use Getopt::Long            ();
use Whostmgr::WPTK::Fcgi    ();

use DateTime                  ();
use DateTime::Duration        ();
use DateTime::Format::RFC3339 ();

use Simple::Accessor qw{
  verbose
  dry_run

  _logger
  cli
  fcgi
};

use constant PID_FILE => q[/var/cpanel/wpsmartphpcleanup.pid];
use constant LOG_DIR  => q[/var/log/wpsmartphpcleanup];

use constant GLOBAL_TIMEOUT          => 12 * 3_600;
use constant RETENTION_TIME_IN_HOURS => 48;

our $VERSION = '1.0';

exit( run(@ARGV) // 0 ) unless caller;

sub run (@args) {

    my $self = __PACKAGE__->new();

    Getopt::Long::GetOptionsFromArray(
        \@args,
        "help|h|?" => \my $print_help,
        "run"      => \my $run,
        "cron"     => \my $cron,
        "dry-run"  => \my $dry_run,
        "verbose!" => \( my $verbose = 1 )
    );

    $verbose = 0 if $cron;
    $run     = 1 if $cron;

    $verbose = 1 if $dry_run;
    $run     = 1 if $dry_run;

    $self->verbose($verbose);
    $self->dry_run($dry_run);

    return usage() if $print_help;
    if ( !$run ) {
        warn "WARNING: Nothing to do, consider using the --run argument.\n";
        return usage(1);
    }

    return 1 unless _acquire_pid();

    return $self->check_instances;
}

sub check_instances ($self) {

    $self->info("Command invoked with --dry-run - no operations will be performed") if $self->dry_run;

    # note: unfortunately we have to check all instances to check their php result.
    #   we cannot loop on the hidden instances, as we cannot get the parent id

    my $list = $self->cli->list_active_instances;

    my $count = scalar $list->@*;
    if ( !$count ) {
        return $self->info("◉ No instances - exiting");
    }

    $self->info("◉ Checking $count instances");

    my $deleted_count = 0;

    my $dt_now = DateTime->now();

    my $retention_duration = DateTime::Duration->new( hours => RETENTION_TIME_IN_HOURS );

    foreach my $e ( $list->@* ) {
        my $id = $e->{id} or next;

        $self->info("⨀ Checking ID: $id");

        my $data = $self->check_smart_php_update($id);
        next unless $data;
        next unless my $ts = $data->{createdAt};

        my $created_at = eval { DateTime::Format::RFC3339->parse_datetime($ts); };
        next unless $created_at;

        $self->info(" ↪ found php smart update instance creation datetime: $created_at");

        my $delta = $dt_now - $created_at;
        if ( DateTime::Duration->compare( $delta, $retention_duration ) == 1 ) {
            $deleted_count++ if $self->remove_smart_php_update($id);
            next;
        }

        $self->info(" ↪ smart php instance is within retention policy");
    }

    if ( $self->dry_run ) {
        $self->info("Δ $deleted_count smart php instances would be removed (command invoked with --dry-run)");
    }
    else {
        $self->info("Δ $deleted_count smart php instances removed");
    }

    return;
}

sub check_smart_php_update ( $self, $instance_id ) {

    my $reply = $self->fcgi->get("/installations/$instance_id/features/updates/php/result");

    return unless $reply && $reply->is_success;
    return $reply->data;
}

sub remove_smart_php_update ( $self, $instance_id ) {

    return unless $instance_id;

    $self->info(" ↪ Removing smart php update for instance $instance_id");
    return 1 if $self->dry_run;

    my $data = {
        'delete' => Cpanel::JSON::true(),
    };

    # note: we should prefer using the methods from wpt.pm moved to a share location
    my $res = $self->fcgi->delete("/installations/$instance_id/features/updates/php/result");
    if ( !$res || !$res->is_success ) {
        $self->error("Failed to remove smart php update for instance id $instance_id");
        return;
    }

    return 1;
}

sub info ( $self, $msg ) {
    return unless $self->verbose && length $msg;
    say $msg;
    return;
}

sub error ( $self, $msg ) {
    return $self->_logger->error($msg);
}

sub _build__logger {
    return Cpanel::Logger->new( { 'timestamp_prefix' => 1 } );
}

sub _build_cli {
    return Cpanel::Binaries::WPTK->new();
}

sub _build_fcgi {
    return Whostmgr::WPTK::Fcgi->new( user => 'root' );
}

sub _acquire_pid {
    my $pidfile    = PID_FILE;
    my $pid_locked = Cpanel::Unix::PID::Tiny::get_run_lock( $pidfile, undef, GLOBAL_TIMEOUT + 1_200 );
    if ( !$pid_locked ) {
        my $running_pid = Cpanel::Unix::PID::Tiny->get_pid_from_pidfile($pidfile) // 0;
        warn("$0 appears to already be running ( process $running_pid holding $pidfile lock )\n");
    }
    return $pid_locked;
}

sub usage ( $status = 0 ) {

    print <<"EOS";
$0 - Enforce Smart PHP Update Cleanup Policy

This script is designed to be run from a crontab to
enforce the Smart PHP Update Cleanup Policy

OPTIONS:
    --help | -h     Display this help message
    --cron          Used by the crontab entry to trigger the backups
                    Auto-enables --no-verbose
    --run           Use that argument to run the backup from the command line
    --dry-run       Run the logic but do not perform any operations
    --no-verbose    Disable detailed output

Example:
# Run from the crontab
$0 --cron

# or run it manually
$0 --run
EOS

    return $status;
}

1;
