#!/usr/bin/perl
#
# SMArT service control helper for MARS_NWE systemd unit.
# Installed next to the other SMArT perl helpers.
#

use strict;
use warnings;

our ($server_id, $mars_nwe_service, $smart_systemctl_path, $cc, %p);

sub html_escape
{
    my ($s) = @_;
    $s = '' unless defined $s;
    $s =~ s/&/&amp;/g;
    $s =~ s/</&lt;/g;
    $s =~ s/>/&gt;/g;
    $s =~ s/"/&quot;/g;
    return $s;
}

sub run_systemctl_command
{
    my ($systemctl, @args) = @_;
    my $output = '';
    my ($reader, $writer);

    if( ! pipe( $reader, $writer ) )
    {
        return (1, 'Could not create pipe: ' . $! . "\n");
    }

    my $pid = fork();
    if( ! defined( $pid ) )
    {
        close( $reader );
        close( $writer );
        return (1, 'Could not fork: ' . $! . "\n");
    }

    if( $pid == 0 )
    {
        close( $reader );

        # Capture stdout for the web UI. Leave stderr untouched so systemctl
        # warnings/errors still go to smart.log, matching the old behaviour.
        open( STDOUT, '>&', $writer ) or exit 127;
        close( $writer );

        exec( $systemctl, @args ) or do {
            print 'Could not execute ' . $systemctl . ': ' . $! . "\n";
            exit 127;
        };
    }

    close( $writer );
    while( my $line = <$reader> )
    {
        $output .= $line;
    }
    close( $reader );

    waitpid( $pid, 0 );
    my $rc = $? >> 8;

    return ( $rc, $output );
}


sub wait_for_service_state
{
    my ($systemctl, $service, $wanted, $timeout) = @_;
    my $elapsed = 0;
    my $last_state = '';
    my $ok = 0;

    $timeout = 45 unless defined($timeout) && $timeout > 0;

    print '# waiting for ' . html_escape($service) . ' to become ' . html_escape($wanted) . "\n";

    while( $elapsed <= $timeout )
    {
        my ($state_rc, $state_output) = run_systemctl_command( $systemctl, 'is-active', $service );
        my $state = $state_output;

        $state =~ s/[\r\n]+$//;
        $state =~ s/^\s+//;
        $state =~ s/\s+$//;
        $state = 'unknown' if $state eq '';

        if( $state ne $last_state )
        {
            print sprintf( "[%2ds] state: %s\n", $elapsed, html_escape($state) );
            $last_state = $state;
        }

        if( $wanted eq 'inactive' )
        {
            if( $state eq 'inactive' || $state eq 'failed' )
            {
                $ok = 1;
                last;
            }
        }
        elsif( $wanted eq 'active' )
        {
            if( $state eq 'active' )
            {
                $ok = 1;
                last;
            }

            if( $state eq 'failed' )
            {
                last;
            }
        }

        sleep( 1 );
        $elapsed++;
    }

    if( $ok )
    {
        print "wait result: reached $wanted\n";
        return 0;
    }

    print "wait result: timeout after $timeout seconds\n";
    return 1;
}

sub append_command_output
{
    my ($title, $rc, $output) = @_;

    print '# ' . html_escape($title) . "\n";
    if( defined($output) && $output ne '' )
    {
        print html_escape($output);
    }
    else
    {
        print "(no output)\n";
    }
    print "exit code: $rc\n";
}

sub handle_request
{
    local $| = 1;

    my $service   = $mars_nwe_service || '@MARS_NWE_SYSTEMD_SERVICE@';
    my $systemctl = $smart_systemctl_path || '@SYSTEMCTL_EXECUTABLE@';
    my $query     = defined($cc) ? $cc : '';
    my $action    = '';

    $query =~ s/^\?//;

    if( $query =~ /^(start|stop|restart|status)$/ )
    {
        $action = $1;
    }
    elsif( $query =~ /(?:^|&)action=(start|stop|restart|status)(?:&|$)/ )
    {
        $action = $1;
    }
    elsif( defined($p{action}) && $p{action} =~ /^(start|stop|restart|status)$/ )
    {
        $action = $1;
    }

    print <<HTML_HEAD;
HTTP/1.0 200 OK
Content-Type: text/html
$server_id

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MARS_NWE service control</title>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
<style>
body{margin:0;padding:22px;background:#f6f1ea;color:#342a25;font-family:Arial,Helvetica,sans-serif}
a{color:#9f1f1f;text-decoration:none;font-weight:bold}a:hover{text-decoration:underline}
.box{max-width:900px;margin:0 auto;background:#fffdfa;border:1px solid #e5d6c6;border-radius:16px;padding:22px;box-shadow:0 10px 30px rgba(60,30,10,.10)}
h1{margin-top:0;color:#9f1f1f}.meta{padding:12px 14px;background:#fbf6ef;border:1px solid #ecdcc8;border-radius:12px;margin-bottom:14px}
pre{background:#2b241f;color:#f8eadc;padding:16px;border-radius:12px;overflow:auto;white-space:pre-wrap}.ok{color:#2f6f35;font-weight:bold}.bad{color:#9f1f1f;font-weight:bold}
.actions a{display:inline-block;margin:5px 8px 5px 0;padding:9px 12px;border-radius:10px;background:#9f1f1f;color:#fff}.actions a.secondary{background:#6f5b4f}
.waitbox{display:flex;align-items:center;gap:14px;margin:14px 0;padding:14px 16px;border:1px solid #ecdcc8;border-radius:14px;background:#fbf6ef;color:#6f5b4f}
.spinner{width:30px;height:30px;border:4px solid #e5d6c6;border-top-color:#9f1f1f;border-radius:50%;animation:spin .9s linear infinite;flex:0 0 auto}
\@keyframes spin{to{transform:rotate(360deg)}}
.waittitle{font-weight:bold;color:#9f1f1f}.waitsub{font-size:13px;margin-top:3px}
</style>
</head>
<body>
<div class="box">
<h1>MARS_NWE service control</h1>
HTML_HEAD

    if( $action eq '' )
    {
        print "<p class=\"bad\">Invalid action.</p>\n";
        print "<p>Allowed actions: start, stop, restart, status</p>\n";
        print "<p><a href=\"/static/start.html\">Back</a></p>\n";
        print "</div></body></html>\n";
        return;
    }

    print '<div class="meta">Action: <b>' . html_escape($action) . '</b><br>' . "\n";
    print 'Service: <b>' . html_escape($service) . '</b><br>' . "\n";
    print 'systemctl: <b>' . html_escape($systemctl) . '</b></div>' . "\n";

    if( $action ne 'status' )
    {
        my $wait_text = 'Waiting for service state change';
        if( $action eq 'stop' )
        {
            $wait_text = 'Stopping service, waiting until it is inactive';
        }
        elsif( $action eq 'start' )
        {
            $wait_text = 'Starting service, waiting until it is active';
        }
        elsif( $action eq 'restart' )
        {
            $wait_text = 'Restarting service, waiting until it is active';
        }

        print '<div id="waitbox" class="waitbox">' . "\n";
        print '<div class="spinner" aria-hidden="true"></div>' . "\n";
        print '<div><div class="waittitle">' . html_escape($wait_text) . '</div>' . "\n";
        print '<div class="waitsub">This can take up to 45 seconds. Please wait until the final status appears below.</div></div>' . "\n";
        print '</div>' . "\n";
        print " " x 4096; # help browsers render the waiting box before the command finishes
    }

    print "<pre>\n";

    my $rc = 0;
    my $output = '';

    if( $action eq 'status' )
    {
        ( $rc, $output ) = run_systemctl_command( $systemctl, '--no-pager', '--full', 'status', $service );
        append_command_output( "systemctl --no-pager --full status $service", $rc, $output );
    }
    else
    {
        # Avoid repeated "unit file changed on disk" warnings after install/update.
        my ( $reload_rc, $reload_output ) = run_systemctl_command( $systemctl, 'daemon-reload' );
        append_command_output( 'systemctl daemon-reload', $reload_rc, $reload_output );
        print "\n";

        ( $rc, $output ) = run_systemctl_command( $systemctl, $action, $service );
        append_command_output( "systemctl $action $service", $rc, $output );
        print "\n";

        if( $rc == 0 )
        {
            my $wanted_state = ( $action eq 'stop' ) ? 'inactive' : 'active';
            my $wait_rc = wait_for_service_state( $systemctl, $service, $wanted_state, 45 );
            print "\n";
            $rc = $wait_rc if $wait_rc != 0;
        }

        my ( $status_rc, $status_output ) = run_systemctl_command( $systemctl, '--no-pager', '--full', 'status', $service );
        append_command_output( "systemctl --no-pager --full status $service", $status_rc, $status_output );
    }

    if( $rc == 0 )
    {
        print "</pre><script>var w=document.getElementById('waitbox');if(w){w.style.display='none';}</script><p class=\"ok\">Command completed successfully.</p>\n";
    }
    else
    {
        print "</pre><script>var w=document.getElementById('waitbox');if(w){w.style.display='none';}</script><p class=\"bad\">Command failed with exit code $rc.</p>\n";
    }

    print <<HTML_FOOT;
<div class="actions">
<a href="/service/control?status" class="secondary">Status</a>
<a href="/service/control?start">Start</a>
<a href="/service/control?stop" class="secondary">Stop</a>
<a href="/service/control?restart">Restart</a>
</div>
<p><a href="/static/start.html">Back</a></p>
</div>
</body>
</html>
HTML_FOOT
}

1;
