package Whostmgr::CometBackup::RestoreRunLogs;

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

=encoding utf-8

=head1 NAME

Whostmgr::CometBackup::RestoreRunLogs - Management interface for Comet restore run logs

=head1 SYNOPSIS

    use Whostmgr::CometBackup::RestoreRunLogs ();
    
    my $logs = Whostmgr::CometBackup::RestoreRunLogs->new();
    
    # Create a new restore job
    my $job_uuid = $logs->create_restore_job(
        restore_type => 'full',
        users        => ['user1', 'user2'],
        initiated_by => 'root',
    );
    
    # Update restore job status
    $logs->update_restore_job_status($job_uuid, 'completed');
    
    # Get restore job history
    my @history = $logs->get_restore_history(limit => 50);

=head1 DESCRIPTION

This module provides a comprehensive interface for managing and querying Comet restore
run logs. It allows tracking of restore operations, including which user initiated
the restore, what accounts were restored, and when the restore occurred.

=cut

use cPstrict;

use Cpanel::DBI::SQLite              ();
use Cpanel::Mkdir                    ();
use DBD::SQLite                      ();
use Time::Piece                      ();
use Data::UUID                       ();
use Whostmgr::CometBackup::Constants ();
use Whostmgr::CometBackup::Logger    ();

=head1 METHODS

=head2 new

Creates a new RestoreRunLogs instance.

    my $logs = Whostmgr::CometBackup::RestoreRunLogs->new();

=cut

sub new ($class) {
    my $self = bless {
        logger => Whostmgr::CometBackup::Logger->new(),
    }, $class;

    $self->_ensure_database();
    return $self;
}

=head2 create_restore_job

Creates a new restore job entry in the database and returns the generated UUID.

    my $job_uuid = $logs->create_restore_job(
        restore_type => 'full',      # 'full' or 'custom'
        users        => ['user1'],   # Array of usernames being restored
        initiated_by => 'root',      # Username who initiated the restore
        run_uuid     => $uuid,       # Optional: specific run UUID to use
    );

Returns the job UUID on success, dies on error.

=cut

sub create_restore_job ( $self, %args ) {
    my $restore_type = $args{restore_type} or die "restore_type parameter required";
    my $users        = $args{users}        or die "users parameter required";
    my $initiated_by = $args{initiated_by} || 'root';

    # Generate a proper UUID for the job
    my $ug       = Data::UUID->new();
    my $job_uuid = $args{run_uuid} || lc( $ug->create_str() );

    my $dbh = $self->_get_dbh();

    my $start_time = time();
    my $users_json = Cpanel::JSON::Dump($users);

    $dbh->do(
        q{INSERT INTO restore_runs (job_uuid, restore_type, start_time, status, users, initiated_by) VALUES (?, ?, ?, ?, ?, ?)},
        {},
        $job_uuid, $restore_type, $start_time, 'running', $users_json, $initiated_by
    );

    $self->{logger}->info( "Created restore job: $job_uuid for users: " . join( ', ', @$users ) . " initiated by: $initiated_by" );

    return $job_uuid;
}

=head2 add_comet_job_id

Adds a Comet job ID to a restore job for tracking.

    $logs->add_comet_job_id($job_uuid, $comet_job_id);

=cut

sub add_comet_job_id ( $self, $job_uuid, $comet_job_id ) {
    my $dbh = $self->_get_dbh();

    # Get current comet_job_ids
    my $sth = $dbh->prepare(q{SELECT comet_job_ids FROM restore_runs WHERE job_uuid = ?});
    $sth->execute($job_uuid);
    my ($current_ids) = $sth->fetchrow_array();

    my $parsed_ids = $current_ids ? ( eval { Cpanel::JSON::Load($current_ids) } || [] ) : [];
    my @job_ids    = @{$parsed_ids};
    push @job_ids, $comet_job_id;

    my $updated_ids = Cpanel::JSON::Dump( \@job_ids );

    $dbh->do(
        q{UPDATE restore_runs SET comet_job_ids = ? WHERE job_uuid = ?},
        {},
        $updated_ids, $job_uuid
    );

    $self->{logger}->debug("Added Comet job ID $comet_job_id to restore job $job_uuid");

    return 1;
}

=head2 update_restore_job_status

Updates the status of a restore job.

    $logs->update_restore_job_status($job_uuid, 'completed');
    $logs->update_restore_job_status($job_uuid, 'failed', 'Error message');

=cut

sub update_restore_job_status ( $self, $job_uuid, $status, $error_message = undef ) {
    my $dbh = $self->_get_dbh();

    my $end_time = time();

    if ($error_message) {
        $dbh->do(
            q{UPDATE restore_runs SET status = ?, end_time = ?, error_message = ? WHERE job_uuid = ?},
            {},
            $status, $end_time, $error_message, $job_uuid
        );
    }
    else {
        $dbh->do(
            q{UPDATE restore_runs SET status = ?, end_time = ? WHERE job_uuid = ?},
            {},
            $status, $end_time, $job_uuid
        );
    }

    $self->{logger}->debug("Updated restore job $job_uuid to status: $status");

    return 1;
}

=head2 get_restore_history

Gets restore job history with optional filtering.

    my @history = $logs->get_restore_history(
        limit        => 50,          # Optional: limit number of results
        status       => 'completed', # Optional: filter by status
        initiated_by => 'root',      # Optional: filter by user who initiated
        start_date   => $timestamp,  # Optional: jobs after this time
        end_date     => $timestamp,  # Optional: jobs before this time
    );

Returns array of restore job hashrefs.

=cut

sub get_restore_history ( $self, %args ) {
    my $limit        = $args{limit} || 50;
    my $status       = $args{status};
    my $initiated_by = $args{initiated_by};
    my $start_date   = $args{start_date};
    my $end_date     = $args{end_date};

    my $dbh = $self->_get_dbh();

    my @where_clauses;
    my @bind_params;

    if ($status) {
        push @where_clauses, 'status = ?';
        push @bind_params,   $status;
    }

    if ($initiated_by) {
        push @where_clauses, 'initiated_by = ?';
        push @bind_params,   $initiated_by;
    }

    if ($start_date) {
        push @where_clauses, 'start_time >= ?';
        push @bind_params,   $start_date;
    }

    if ($end_date) {
        push @where_clauses, 'start_time <= ?';
        push @bind_params,   $end_date;
    }

    my $where_clause = @where_clauses ? 'WHERE ' . join( ' AND ', @where_clauses ) : '';

    my $sql = qq{
        SELECT job_uuid, restore_type, start_time, end_time, status, 
               error_message, users, initiated_by, comet_job_ids
        FROM restore_runs
        $where_clause
        ORDER BY start_time DESC
        LIMIT ?
    };

    push @bind_params, $limit;

    my $sth = $dbh->prepare($sql);
    $sth->execute(@bind_params);

    my @history;
    while ( my $row = $sth->fetchrow_hashref() ) {

        # Parse users JSON
        $row->{users} = eval { Cpanel::JSON::Load( $row->{users} ) } || [];

        # Parse comet_job_ids JSON
        $row->{comet_job_ids} = eval { Cpanel::JSON::Load( $row->{comet_job_ids} ) } || [] if $row->{comet_job_ids};

        # Calculate duration
        if ( $row->{end_time} && $row->{start_time} ) {
            $row->{duration}           = $row->{end_time} - $row->{start_time};
            $row->{duration_formatted} = $self->_format_duration( $row->{duration} );
        }

        # Format timestamps
        $row->{start_time_formatted} = $self->_format_timestamp( $row->{start_time} );
        $row->{end_time_formatted}   = $self->_format_timestamp( $row->{end_time} ) if $row->{end_time};

        push @history, $row;
    }

    return @history;
}

=head2 get_restore_job

Gets details for a specific restore job by UUID.

    my $job = $logs->get_restore_job($job_uuid);

Returns a hashref with job details or undef if not found.

=cut

sub get_restore_job ( $self, $job_uuid ) {
    my $dbh = $self->_get_dbh();

    my $sth = $dbh->prepare(
        q{
        SELECT job_uuid, restore_type, start_time, end_time, status,
               error_message, users, initiated_by, comet_job_ids
        FROM restore_runs
        WHERE job_uuid = ?
    }
    );

    $sth->execute($job_uuid);
    my $row = $sth->fetchrow_hashref();

    return unless $row;

    # Parse users JSON
    $row->{users} = eval { Cpanel::JSON::Load( $row->{users} ) } || [];

    # Parse comet_job_ids JSON
    $row->{comet_job_ids} = eval { Cpanel::JSON::Load( $row->{comet_job_ids} ) } || [] if $row->{comet_job_ids};

    # Calculate duration
    if ( $row->{end_time} && $row->{start_time} ) {
        $row->{duration}           = $row->{end_time} - $row->{start_time};
        $row->{duration_formatted} = $self->_format_duration( $row->{duration} );
    }

    # Format timestamps
    $row->{start_time_formatted} = $self->_format_timestamp( $row->{start_time} );
    $row->{end_time_formatted}   = $self->_format_timestamp( $row->{end_time} ) if $row->{end_time};

    return $row;
}

=head1 PRIVATE METHODS

=head2 _get_dbh

Gets a database handle for the restore logs database.

=cut

sub _get_dbh ($self) {
    return Cpanel::DBI::SQLite->connect(
        {
            database   => $Whostmgr::CometBackup::Constants::RUN_LOG_DB,
            RaiseError => 1,
        }
    );
}

=head2 _ensure_database

Ensures the restore logs database and tables exist.

=cut

sub _ensure_database ($self) {

    # Ensure directory exists
    my $db_dir = $Whostmgr::CometBackup::Constants::RUN_LOG_DB;
    $db_dir =~ s{/[^/]+$}{};

    Cpanel::Mkdir::ensure_directory_existence_and_mode( $db_dir, 0755 ) unless -d $db_dir;

    my $dbh = $self->_get_dbh();

    # Create restore_runs table if it doesn't exist
    $dbh->do(<<~'SQL');
        CREATE TABLE IF NOT EXISTS restore_runs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            job_uuid TEXT NOT NULL UNIQUE,
            restore_type TEXT NOT NULL,
            start_time INTEGER NOT NULL,
            end_time INTEGER,
            status TEXT DEFAULT 'running',
            error_message TEXT,
            users TEXT NOT NULL,
            initiated_by TEXT NOT NULL,
            comet_job_ids TEXT
        )
        SQL

    # Create indexes
    $dbh->do("CREATE INDEX IF NOT EXISTS idx_restore_runs_job_uuid ON restore_runs(job_uuid)");
    $dbh->do("CREATE INDEX IF NOT EXISTS idx_restore_runs_start_time ON restore_runs(start_time)");
    $dbh->do("CREATE INDEX IF NOT EXISTS idx_restore_runs_initiated_by ON restore_runs(initiated_by)");
    $dbh->do("CREATE INDEX IF NOT EXISTS idx_restore_runs_status ON restore_runs(status)");

    # Migrate existing tables: add comet_job_ids column if it doesn't exist
    my $has_comet_job_ids = eval {
        my $sth = $dbh->prepare("SELECT comet_job_ids FROM restore_runs LIMIT 1");
        $sth->execute();
        1;
    };

    unless ($has_comet_job_ids) {
        $self->{logger}->debug("Migrating restore_runs table: adding comet_job_ids column");
        eval { $dbh->do("ALTER TABLE restore_runs ADD COLUMN comet_job_ids TEXT"); };
        if ($@) {
            $self->{logger}->warn("Failed to add comet_job_ids column (may already exist): $@");
        }
    }

    $dbh->disconnect();

    return;
}

=head2 _format_timestamp

Formats a Unix timestamp into a human-readable string.

=cut

sub _format_timestamp ( $self, $timestamp ) {
    return unless $timestamp;

    my $time = Time::Piece->new($timestamp);
    return $time->strftime('%Y-%m-%d %H:%M:%S');
}

=head2 _format_duration

Formats a duration in seconds into a human-readable string.

=cut

sub _format_duration ( $self, $seconds ) {
    return unless defined $seconds;

    my $hours = int( $seconds / 3600 );
    $seconds %= 3600;
    my $minutes = int( $seconds / 60 );
    $seconds %= 60;

    if ( $hours > 0 ) {
        return sprintf( "%dh %02dm %02ds", $hours, $minutes, $seconds );
    }
    elsif ( $minutes > 0 ) {
        return sprintf( "%dm %02ds", $minutes, $seconds );
    }
    else {
        return sprintf( "%ds", $seconds );
    }
}

1;
