#!/usr/local/cpanel/3rdparty/bin/perl
# gniza4cp WHM Plugin — Activity Logs
# View backup and cron log files (read-only)
use strict;
use warnings;

use lib '/usr/local/cpanel/whostmgr/docroot/cgi/gniza4cp-whm/lib';

use Whostmgr::HTMLInterface ();
use Cpanel::Form            ();
use Gniza4cpWHM::Config;
use Gniza4cpWHM::UI;

my $form = Cpanel::Form::parseform();

# Determine log directory from config
my $log_dir   = '/var/log/gniza4cp';
my $main_conf = '/etc/gniza4cp/gniza4cp.conf';
if (-f $main_conf) {
    my $cfg = Gniza4cpWHM::Config::parse($main_conf, 'main');
    $log_dir = $cfg->{LOG_DIR} if $cfg->{LOG_DIR} && $cfg->{LOG_DIR} ne '';
}

my $file = $form->{'file'} // '';

if ($file ne '') {
    show_file($file);
} else {
    show_list();
}

exit;

# ── File List View ────────────────────────────────────────────

sub show_list {
    print "Content-Type: text/html\r\n\r\n";
    Whostmgr::HTMLInterface::defheader('gniza4cp — Logs');
    print Gniza4cpWHM::UI::page_header('GNIZA Backup Manager');
    print Gniza4cpWHM::UI::render_nav('logs.cgi');
    print Gniza4cpWHM::UI::render_flash();

    unless (-d $log_dir) {
        print qq{<div class="alert alert-info mb-4">No log directory found at <code>}
            . Gniza4cpWHM::UI::esc($log_dir)
            . qq{</code>.</div>\n};
        print Gniza4cpWHM::UI::page_footer();
        return;
    }

    # Read log files
    my @files;
    if (opendir my $dh, $log_dir) {
        while (my $entry = readdir $dh) {
            next unless $entry =~ /^gniza4cp-\d{8}-\d{6}\.log$/ || $entry =~ /^cpanel-[a-z][a-z0-9_-]*\.log$/;
            my $path = "$log_dir/$entry";
            next unless -f $path;
            my @stat = stat($path);
            my ($type, $status, $owner);
            if ($entry =~ /^cpanel-([a-z][a-z0-9_-]*)\.log$/) {
                $type   = 'User';
                $owner  = $1;
                $status = _detect_activity_status($path);
            } else {
                $type   = _detect_log_type($path);
                $status = _detect_log_status($path);
                $owner  = '';
            }
            push @files, {
                name   => $entry,
                size   => $stat[7] // 0,
                mtime  => $stat[9] // 0,
                type   => $type,
                status => $status,
                owner  => $owner,
            };
        }
        closedir $dh;
    }

    # Sort by mtime descending
    @files = sort { $b->{mtime} <=> $a->{mtime} } @files;

    if (!@files) {
        print qq{<div class="alert alert-info mb-4">No log files found.</div>\n};
        print Gniza4cpWHM::UI::page_footer();
        return;
    }

    # Pagination
    my $per_page   = 25;
    my $total      = scalar @files;
    my $page       = int($form->{'page'} // 1);
    $page = 1 if $page < 1;
    my $total_pages = int(($total + $per_page - 1) / $per_page);
    $page = $total_pages if $page > $total_pages;
    my $start = ($page - 1) * $per_page;
    my $end   = $start + $per_page - 1;
    $end = $#files if $end > $#files;
    my @page_files = @files[$start .. $end];

    print qq{<div class="overflow-x-auto rounded-box border border-base-content/5 bg-base-100">\n};
    print qq{<table class="table">\n};
    print qq{<thead><tr><th>Filename</th><th>Type</th><th>Owner</th><th>Status</th><th>Date</th><th>Size</th><th></th></tr></thead>\n};
    print qq{<tbody>\n};

    for my $f (@page_files) {
        my $esc_name = Gniza4cpWHM::UI::esc($f->{name});
        my $badge    = $f->{type} eq 'Cron'   ? 'badge-neutral'
                     : $f->{type} eq 'System' ? 'badge-warning'
                     : $f->{type} eq 'User'   ? 'badge-secondary'
                     :                          'badge-info';
        my $date     = _format_time($f->{mtime});
        my $size     = _human_size($f->{size});
        my $href     = 'logs.cgi?file=' . _uri_escape($f->{name});

        print qq{<tr>\n};
        print qq{  <td><code>$esc_name</code></td>\n};
        print qq{  <td><span class="badge $badge badge-sm">$f->{type}</span></td>\n};
        my $esc_owner = Gniza4cpWHM::UI::esc($f->{owner} // '');
        print qq{  <td>$esc_owner</td>\n};
        my $status_badge = $f->{status} eq 'Error'   ? 'badge-error'
                         : $f->{status} eq 'Warning' ? 'badge-warning'
                         :                             'badge-success';
        print qq{  <td><span class="badge $status_badge badge-sm">$f->{status}</span></td>\n};
        print qq{  <td>$date</td>\n};
        print qq{  <td>$size</td>\n};
        print qq{  <td><button type="button" class="btn btn-secondary btn-sm" onclick="location.href='$href'">View</button></td>\n};
        print qq{</tr>\n};
    }

    print qq{</tbody>\n</table>\n</div>\n};

    # Pagination controls
    if ($total_pages > 1) {
        print qq{<div class="flex items-center justify-center gap-2 mt-4">\n};
        if ($page > 1) {
            my $prev = $page - 1;
            print qq{  <button type="button" class="btn btn-sm" onclick="location.href='logs.cgi?page=$prev'">&laquo; Prev</button>\n};
        }
        print qq{  <span class="text-sm">Page $page of $total_pages ($total logs)</span>\n};
        if ($page < $total_pages) {
            my $next = $page + 1;
            print qq{  <button type="button" class="btn btn-sm" onclick="location.href='logs.cgi?page=$next'">Next &raquo;</button>\n};
        }
        print qq{</div>\n};
    }

    # Auto-refresh while gniza4cp is running
    print qq{<script>
(function() {
    var key = 'gniza4cp_wasRunning';
    fetch('index.cgi?action=status')
        .then(function(r) { return r.json(); })
        .then(function(d) {
            if (d.running) {
                sessionStorage.setItem(key, '1');
                setTimeout(function() { location.reload(); }, 10000);
            } else if (sessionStorage.getItem(key) === '1') {
                sessionStorage.removeItem(key);
                location.reload();
            }
        })
        .catch(function() {});
})();
</script>\n};

    print Gniza4cpWHM::UI::page_footer();
}

# ── File View ─────────────────────────────────────────────────

sub show_file {
    my ($filename) = @_;

    print "Content-Type: text/html\r\n\r\n";
    Whostmgr::HTMLInterface::defheader('gniza4cp — Log Viewer');
    print Gniza4cpWHM::UI::page_header('GNIZA Backup Manager');
    print Gniza4cpWHM::UI::render_nav('logs.cgi');

    # Validate filename (prevents path traversal)
    unless (_valid_log_filename($filename)) {
        print qq{<div class="alert alert-error mb-4">Invalid log filename.</div>\n};
        print qq{<p><a href="logs.cgi" class="link">&larr; Back to logs</a></p>\n};
        print Gniza4cpWHM::UI::page_footer();
        return;
    }

    my $filepath = "$log_dir/$filename";
    unless (-f $filepath) {
        print qq{<div class="alert alert-error mb-4">Log file not found.</div>\n};
        print qq{<p><a href="logs.cgi" class="link">&larr; Back to logs</a></p>\n};
        print Gniza4cpWHM::UI::page_footer();
        return;
    }

    # Detect activity log vs standard log
    if ($filename =~ /^cpanel-([a-z][a-z0-9_-]*)\.log$/) {
        _show_activity_file($filename, $filepath, $1);
        return;
    }

    # Read file
    my @lines;
    if (open my $fh, '<', $filepath) {
        @lines = <$fh>;
        close $fh;
    }
    chomp @lines;

    my $total_lines  = scalar @lines;
    my $is_cron      = ($filename =~ /^cron-/);
    my $show_all     = ($form->{'all'} // '') eq '1';
    my $truncated    = 0;
    my $max_lines    = 10_000;
    my $cron_default = 500;

    # Apply line limits
    if ($is_cron && !$show_all && $total_lines > $cron_default) {
        @lines = @lines[-$cron_default .. -1];
        $truncated = 1;
    }
    if (@lines > $max_lines) {
        @lines = @lines[-$max_lines .. -1];
        $truncated = 1;
    }

    # Level filter
    my $level_filter = $form->{'level'} // '';
    $level_filter =~ s/\s+//g;
    $level_filter = '' unless $level_filter =~ /^(error|warn|info|debug)$/i;
    $level_filter = uc($level_filter) if $level_filter;

    # File info
    my @stat     = stat($filepath);
    my $file_size = _human_size($stat[7] // 0);
    my $file_date = _format_time($stat[9] // 0);
    my $esc_name  = Gniza4cpWHM::UI::esc($filename);

    # Back link
    print qq{<p class="mb-4"><a href="logs.cgi" class="link">&larr; Back to logs</a></p>\n};

    # File info card
    print qq{<div class="card bg-base-200 shadow-sm border border-base-300 mb-4">\n};
    print qq{<div class="card-body py-3 px-4">\n};
    print qq{<div class="flex flex-wrap gap-3 items-center text-sm">\n};
    print qq{  <span><strong>File:</strong> <code>$esc_name</code></span>\n};
    print qq{  <span><strong>Size:</strong> $file_size</span>\n};
    print qq{  <span><strong>Lines:</strong> $total_lines</span>\n};
    print qq{  <span><strong>Date:</strong> $file_date</span>\n};
    print qq{</div>\n</div>\n</div>\n};

    # Truncation alert
    if ($truncated) {
        my $all_href = 'logs.cgi?file=' . _uri_escape($filename) . '&all=1';
        $all_href .= '&level=' . lc($level_filter) if $level_filter;
        print qq{<div class="alert alert-warning mb-4">Showing last }
            . scalar(@lines)
            . qq{ of $total_lines lines. }
            . qq{<a href="$all_href" class="link font-bold">View all</a></div>\n};
    }

    # Level filter buttons
    my $base_href = 'logs.cgi?file=' . _uri_escape($filename);
    $base_href .= '&all=1' if $show_all;

    print qq{<div class="flex gap-1 mb-4">\n};
    my @levels = ('', 'error', 'warn', 'info', 'debug');
    my %level_labels = ('' => 'All', error => 'Error', warn => 'Warn', info => 'Info', debug => 'Debug');
    for my $lv (@levels) {
        my $active = '';
        if ($lv eq '' && $level_filter eq '') {
            $active = ' btn-active';
        } elsif ($lv ne '' && uc($lv) eq $level_filter) {
            $active = ' btn-active';
        }
        my $href = $base_href;
        $href .= "&level=$lv" if $lv ne '';
        my $label = $level_labels{$lv};
        print qq{  <button type="button" class="btn btn-sm$active" onclick="location.href='$href'">$label</button>\n};
    }
    print qq{</div>\n};

    # Filter lines by level
    my @display_lines;
    if ($level_filter) {
        @display_lines = grep { /\[$level_filter\]/ } @lines;
    } else {
        @display_lines = @lines;
    }

    # Render log content
    if (!@display_lines) {
        print qq{<div class="alert alert-info">No matching log entries.</div>\n};
    } else {
        print qq{<pre class="bg-base-100 border border-base-300 rounded-lg p-4 text-sm font-mono overflow-x-auto leading-relaxed">};
        for my $line (@display_lines) {
            my $esc = Gniza4cpWHM::UI::esc($line);
            if ($line =~ /\[ERROR\]/) {
                print qq{<span class="text-error font-bold">$esc</span>\n};
            } elsif ($line =~ /\[WARN\]/) {
                print qq{<span class="text-warning">$esc</span>\n};
            } elsif ($line =~ /\[DEBUG\]/) {
                print qq{<span class="text-base-content/60">$esc</span>\n};
            } else {
                print "$esc\n";
            }
        }
        print qq{</pre>\n};
    }

    # Auto-refresh + auto-scroll while gniza4cp is running
    print qq{<script>
(function() {
    var key = 'gniza4cp_wasRunning_file';
    fetch('index.cgi?action=status')
        .then(function(r) { return r.json(); })
        .then(function(d) {
            if (d.running) {
                sessionStorage.setItem(key, '1');
                window.scrollTo(0, document.body.scrollHeight);
                setTimeout(function() { location.reload(); }, 5000);
            } else if (sessionStorage.getItem(key) === '1') {
                sessionStorage.removeItem(key);
                location.reload();
            }
        })
        .catch(function() {});
})();
</script>\n};

    print Gniza4cpWHM::UI::page_footer();
}

# ── Activity Log Viewer (cpanel-*.log) ───────────────────────

sub _show_activity_file {
    my ($filename, $filepath, $owner) = @_;

    my $esc_name  = Gniza4cpWHM::UI::esc($filename);
    my $esc_owner = Gniza4cpWHM::UI::esc($owner);
    my @stat      = stat($filepath);
    my $file_size = _human_size($stat[7] // 0);
    my $file_date = _format_time($stat[9] // 0);

    # Back link
    print qq{<p class="mb-4"><a href="logs.cgi" class="link">&larr; Back to logs</a></p>\n};

    # File info card
    print qq{<div class="card bg-base-200 shadow-sm border border-base-300 mb-4">\n};
    print qq{<div class="card-body py-3 px-4">\n};
    print qq{<div class="flex flex-wrap gap-3 items-center text-sm">\n};
    print qq{  <span><strong>User:</strong> $esc_owner</span>\n};
    print qq{  <span><strong>File:</strong> <code>$esc_name</code></span>\n};
    print qq{  <span><strong>Size:</strong> $file_size</span>\n};
    print qq{  <span><strong>Last Modified:</strong> $file_date</span>\n};
    print qq{</div>\n</div>\n</div>\n};

    # Parse activity entries
    my @entries;
    if (open my $fh, '<', $filepath) {
        my $in_entry = 0;
        my (%cur, @cur_output);
        while (my $line = <$fh>) {
            chomp $line;
            if ($line eq '--- ENTRY ---') {
                $in_entry = 1;
                %cur = ();
                @cur_output = ();
            } elsif ($line eq '--- END ---' && $in_entry) {
                $cur{output} = join("\n", @cur_output) if @cur_output;
                push @entries, { %cur } if $cur{date};
                $in_entry = 0;
            } elsif ($in_entry) {
                if    ($line =~ /^Date:\s+(.+)$/)    { $cur{date}    = $1; }
                elsif ($line =~ /^Action:\s+(.+)$/)   { $cur{action}  = $1; }
                elsif ($line =~ /^Details:\s+(.+)$/)  { $cur{details} = $1; }
                elsif ($line =~ /^Status:\s+(.+)$/)   { $cur{status}  = $1; }
                else { push @cur_output, $line; }
            }
        }
        close $fh;
    }

    # Show newest first
    @entries = reverse @entries;

    if (!@entries) {
        print qq{<div class="alert alert-info">No activity entries found.</div>\n};
        print Gniza4cpWHM::UI::page_footer();
        return;
    }

    # Pagination
    my $per_page    = 25;
    my $total       = scalar @entries;
    my $page        = int($form->{'page'} // 1);
    $page = 1 if $page < 1;
    my $total_pages = int(($total + $per_page - 1) / $per_page);
    $page = $total_pages if $page > $total_pages;
    my $start = ($page - 1) * $per_page;
    my $end   = $start + $per_page - 1;
    $end = $#entries if $end > $#entries;
    my @page_entries = @entries[$start .. $end];

    for my $e (@page_entries) {
        my $status_badge = ($e->{status} // '') eq 'Error' ? 'badge-error' : 'badge-success';
        my $esc_date    = Gniza4cpWHM::UI::esc($e->{date}    // '');
        my $esc_action  = Gniza4cpWHM::UI::esc($e->{action}  // '');
        my $esc_details = Gniza4cpWHM::UI::esc($e->{details} // '');
        my $esc_status  = Gniza4cpWHM::UI::esc($e->{status}  // '');

        print qq{<div class="card bg-base-100 border border-base-300 mb-3">\n};
        print qq{<div class="card-body py-3 px-4">\n};
        print qq{<div class="flex flex-wrap gap-3 items-center text-sm mb-1">\n};
        print qq{  <span>$esc_date</span>\n};
        print qq{  <span class="badge badge-info badge-sm">$esc_action</span>\n};
        print qq{  <span class="badge $status_badge badge-sm">$esc_status</span>\n};
        print qq{</div>\n};
        print qq{<div class="text-sm text-base-content/70">$esc_details</div>\n};

        if ($e->{output} && $e->{output} ne '') {
            print qq{<details class="mt-2">\n};
            print qq{<summary class="cursor-pointer text-sm font-medium">Command Output</summary>\n};
            print qq{<pre class="bg-base-200 rounded p-3 mt-1 text-xs font-mono overflow-x-auto leading-relaxed">};
            for my $oline (split /\n/, $e->{output}) {
                my $esc = Gniza4cpWHM::UI::esc($oline);
                if ($oline =~ /\[ERROR\]/) {
                    print qq{<span class="text-error font-bold">$esc</span>\n};
                } elsif ($oline =~ /\[WARN\]/) {
                    print qq{<span class="text-warning">$esc</span>\n};
                } else {
                    print "$esc\n";
                }
            }
            print qq{</pre>\n};
            print qq{</details>\n};
        }

        print qq{</div>\n</div>\n};
    }

    # Pagination controls
    if ($total_pages > 1) {
        my $base = 'logs.cgi?file=' . _uri_escape($filename);
        print qq{<div class="flex items-center justify-center gap-2 mt-4">\n};
        if ($page > 1) {
            my $prev = $page - 1;
            print qq{  <button type="button" class="btn btn-sm" onclick="location.href='$base&page=$prev'">&laquo; Prev</button>\n};
        }
        print qq{  <span class="text-sm">Page $page of $total_pages ($total entries)</span>\n};
        if ($page < $total_pages) {
            my $next = $page + 1;
            print qq{  <button type="button" class="btn btn-sm" onclick="location.href='$base&page=$next'">Next &raquo;</button>\n};
        }
        print qq{</div>\n};
    }

    print Gniza4cpWHM::UI::page_footer();
}

# ── Helpers ───────────────────────────────────────────────────

sub _valid_log_filename {
    my ($name) = @_;
    return 0 unless defined $name && $name ne '';
    return 1 if $name =~ /^gniza4cp-\d{8}-\d{6}\.log$/;
    return 1 if $name =~ /^cron-[a-zA-Z0-9_-]+\.log$/;
    return 1 if $name =~ /^cpanel-[a-z][a-z0-9_-]*\.log$/;
    return 0;
}

sub _detect_activity_status {
    my ($path) = @_;
    return 'Success' unless -f $path;
    my $has_error = 0;
    if (open my $fh, '<', $path) {
        while (my $line = <$fh>) {
            if ($line =~ /^Status:\s+Error$/) {
                $has_error = 1;
                last;
            }
        }
        close $fh;
    }
    return $has_error ? 'Error' : 'Success';
}

sub _detect_log_type {
    my ($path) = @_;
    if (open my $fh, '<', $path) {
        while (my $line = <$fh>) {
            if ($line =~ /\[TYPE:SYSBACKUP\]/) {
                close $fh;
                return 'System';
            }
            last if $. > 5;
        }
        close $fh;
    }
    return 'Backup';
}

sub _detect_log_status {
    my ($path) = @_;
    return 'Success' unless -f $path;
    my $has_warn = 0;
    if (open my $fh, '<', $path) {
        while (my $line = <$fh>) {
            if ($line =~ /\[ERROR\]/) {
                close $fh;
                return 'Error';
            }
            $has_warn = 1 if !$has_warn && $line =~ /\[WARN\]/;
        }
        close $fh;
    }
    return $has_warn ? 'Warning' : 'Success';
}

sub _uri_escape {
    my $str = shift // '';
    $str =~ s/([^A-Za-z0-9\-._~])/sprintf("%%%02X", ord($1))/ge;
    return $str;
}

sub _format_time {
    my ($epoch) = @_;
    return '' unless $epoch;
    my @t = localtime($epoch);
    return sprintf('%02d/%02d/%04d %02d:%02d:%02d', $t[3], $t[4]+1, $t[5]+1900, $t[2], $t[1], $t[0]);
}

sub _human_size {
    my ($bytes) = @_;
    return '0 B' unless $bytes;
    my @units = ('B', 'KB', 'MB', 'GB');
    my $i = 0;
    my $size = $bytes;
    while ($size >= 1024 && $i < $#units) {
        $size /= 1024;
        $i++;
    }
    return ($i == 0) ? "$size B" : sprintf('%.1f %s', $size, $units[$i]);
}

1;
