#!env perl
use strict;
use warnings;
use subs 'log';
use Fcntl ':flock', ':seek';
use IO::Handle;
use IO::Socket;
use Net::MitDK;
use Getopt::Long;

# */10 * * * * nobody mitdk-renew-lease -a

my %opt = (
	profile    => 'default',
	help       => 0,
	all        => 0,
	verbose    => 0,
	loop       => 0,
	port       => 0,
	dry        => 0,
);

sub usage
{
	print <<USAGE;

$0

   --profile       - Profile name ( $opt{profile} )
   --all|-a        - All profiles
   --dry|-d        - Do not actually renew the lease, run readonly
   --verbose|-v    - Verbose
   --loop|-l       - Never exit, renew every 10 minutes
   --port|-p PORT  - Listen on PORT as control connection
   --help

USAGE
	exit 1;
}

GetOptions(\%opt,
	"help|h",
	"all|a",
	"dry|d",
	"verbose|v",
	"loop|l",
	"profile|s=s",
	"port|p=s",
) or usage;

$opt{help} and usage();

sub log($)
{
	print "$_[0]\n" if $opt{verbose};
}

sub renew
{
	my $profile = shift;
	my ($e, $error) = Net::MitDK->new(profile => $profile);
	die "error (profile=$profile): $error\n" unless $e;
	die "error (profile=$profile): bad profile\n" unless ref($e->config) eq 'HASH';

	unless ( $e->token ) {
		log "$profile: no first login yet";
		return;
	}

	log "loaded profile $profile, expires on " . localtime($e->token->{dpp}->{expires_on});

	if ( $opt{dry}) {
		log "not renewed as running read-only";
		return;
	}


	my $ok;
	($ok, $error) = $e->renew_lease->wait;
	if ( !$ok ) {
		if ( $error =~ /(Rest Exception\: \<ServerErrorException\>|Status 500|Internal Server Error)/) {
			# everyone should wrap xml errors in json
			log "skipping error: $error";
			return;
		}
		my $newerr = $error;
		$newerr =~ s/traceId\:\s+\S+//g;
		if (( $e->config->{renewer}->{error} // '') eq $newerr) {
			log "Skipping repetitive error $error";
			return;
		}

		$e->config->{renewer}->{error} = $newerr;
		$e->update_config;
		die "error (profile=$profile): $error\n";
	} elsif ( defined $e->config->{renewer}->{error}) {
		delete $e->config->{renewer}->{error};
		$e->update_config;
	}

	log "renewed, expires on " . localtime($e->token->{dpp}->{expires_on});
}

my ($server, $sno);
if ( $opt{port} ) {
	unless ($opt{loop}) {
		warn "--port ignored without --loop\n";
	} else {
		$server = IO::Socket::INET-> new(
			Listen    => 5,
			LocalAddr => '0.0.0.0',
			LocalPort => $opt{port},
			Blocking  => 0,
			ReuseAddr => ($^O !~ /win32/i),
		);
		die "Cannot listen on port $opt{port} : $!\n" unless $server;
		log "Listening on $opt{port}";
		$sno = '';
		vec($sno, fileno($server), 1) = 1;
	}
}

my $lockfile;
if ($opt{loop}) {
	my $path = Net::MitDK::ProfileManager->new->homepath;
	$lockfile = "$path/renew-lease.lock";
	if ( open F, "<", $lockfile ) {
		die "Error: another instance is running\n" unless flock( F, LOCK_NB | LOCK_EX);
	} elsif ( ! open F, ">", $lockfile) {
		die "Error: cannot create $lockfile\n";
	} else {
		die "Error: cannot lock $lockfile\n" unless flock(F, LOCK_NB | LOCK_EX);
	}
}
END { unlink $lockfile if defined $lockfile };

AGAIN:
if ( $opt{all}) {
	my $exitcode = 0;
	for (Net::MitDK::ProfileManager->new->list) {
		eval { renew($_) };
		next unless $@;
		warn $@;
		$exitcode = 1;
	}
	exit $exitcode unless $opt{loop};
} else {
	eval { renew($opt{profile}); };
	if ( $@ ) {
		if ( $opt{loop} ) {
			warn $@;
		} else {
			die $@;
		}
	}
}
if ($opt{loop}) {
	my $timeout = 600;
	if ( $sno ) {
		my $R = $sno;
		my $n = select($R, undef, undef, $timeout);
		if ( $n ) {
			my $h = IO::Handle-> new;
			log "New control connection";
			if ( accept( $h, $server) ) {
				my $cmd = <$h>;
				chomp $cmd;
				log "Got command: $cmd";
				close $h;
				if ( $cmd eq 'stop') {
					log "Exiting";
					exit(0);
				}
			}
		}
	} else {
		sleep($timeout);
	}
	goto AGAIN;
}
