#! /usr/bin/env perl
use Mojo::Base -strict, -signatures;

use FindBin;
use Mojo::File 'path';
use Mojo::Util qw|decode encode extract_usage getopt|;
use Parallel::ForkManager;
use Tekki::IO::Colored;
use YAML::XS;

chdir $FindBin::Bin;

my $config = Load path('testconfig.yml')->slurp or die 'Config file not found!';

getopt
  'd|daemon'     => \$config->{daemon},
  'e|execute'    => \$config->{execute},
  'f|forks'      => \my $forks,
  's|service=s'  => \$config->{service},
  't|target=s'   => \$config->{target},
  'v|verbose'    => \$config->{verbose},
  'vv|verbose2'  => \$config->{verbose2},
  'vvv|verbose3' => \$config->{verbose3};

$config->{cmd}
  = ['docker-compose', '-f', $config->{compose_file}, '-p', $config->{prefix}];
$config->{forks} = $forks if $forks;
$config->{verbose2} = 1 if $config->{verbose3} || $config->{execute};

my $arg0 = $ARGV[0] || '';
if ($arg0 =~ /up|down|start|stop|exec/) { run_command($config, $arg0) }
else { test($config) }

sub run_command ($config, $command) {
  my @cmd = ($command);
  if ($command eq 'up') {
    push @cmd, '-d' if $config->{daemon};
  }
  elsif ($command eq 'exec') {
    shift @ARGV;
    push @cmd, @ARGV;
  }
  system($config->{cmd}->@*, @cmd) == 0 or die $!;
}

sub test ($config) {
  my @services = _select_services($config);

  my @testcommand = _create_command($config);
  my @targets;
  push @targets, $_ for _select_targets($config);

  my %results;
  my $pm = Parallel::ForkManager->new(max_proc => $config->{forks});
  $pm->run_on_finish(
    sub ($pid, $exit_code, $ident, $exit_signal, $core_dump, $rvref) {
      if (defined $rvref) {
        $results{$ident} = $rvref;
      }
      else {
        say_warn qq|$ident didn't return anything.|;
      }
    }
  );

  my $first = 1;

TESTRUN:
  for my $service (@services) {
    sleep $config->{start_delay} unless $first;
    $first = 0;

    $pm->start($service) and next TESTRUN;

    my $command = join ' ', $config->{cmd}->@*, 'exec', $service, @testcommand;
    my $result;
    if (@targets) {
      my $init = 1;
      for my $target (@targets) {
        say "$service $target";
        my $target_command = "$command --target $target";
        if ($init) {
          $target_command .= ' --init';
          $init = 0;
        }
        my $output = `$target_command`;
        $result->{$target} = {output => $output, rc => $? >> 8};
      }
    }
    else {
      say $service;
      my $output = `$command`;
      $result = {output => $output, rc => $? >> 8};
    }
    $pm->finish(0, $result);
  }
  $pm->wait_all_children;

  if (@targets) {
    _display_nested_results($config, \@targets, \%results);
  }
  else {
    _display_flat_results($config, \%results);
  }
}

sub _create_command ($config) {
  my @testcommand;
  unless ($config->{execute}) {
    @testcommand = $config->{command} ? ($config->{command}) : qw|prove -Ilib|;
    push @testcommand, '-v' if $config->{verbose3};
  }

  push @testcommand, @ARGV if @ARGV;
  return @testcommand;
}

sub _display_flat_results ($config, $results) {
  system 'reset';    # reset terminal, it gets messed up by Parallel::ForkManager

  if ($config->{verbose} || $config->{verbose2}) {
    for (sort keys $results->%*) {
      if ($config->{verbose2} || $results->{$_} !~ /Result: PASS/) {
        say_info $_;
        say $results->{$_}{output};
      }
    }
  }

  say 'Summary';
  my $format = "%-10s %5s %5s %s\n";
  printf $format, qw|Service Files Tests Result|;
  for my $service (sort keys $results->%*) {
    my $ok

      # = $results->{$service} =~ /Result: PASS/
      # ? color_ok('  OK')
      # : color_error('Error');
      = $results->{$service}{rc} ? color_error('Error') : color_ok('  OK');
    my ($files, $tests)
      = $results->{$service}{output} =~ /Files=(\d+), Tests=(\d+)/;
    printf $format, $service, $files // '', $tests // '', $ok;
  }
}

sub _display_nested_results ($config, $targets, $results) {
  system 'reset';    # reset terminal, it gets messed up by Parallel::ForkManager

  if ($config->{verbose} || $config->{verbose2}) {
    for my $service (sort keys $results->%*) {
      for my $target (sort keys $results->{$service}->%*) {
        if ( $config->{verbose2}
          || $results->{$service}{$target}{output} !~ /Result: PASS/)
        {
          say_info "$service $target";
          say $results->{$service}{$target}{output};
        }
      }
    }
  }

  my $target_count  = $targets->@*;
  my $target_header = '  %18s' x $target_count;
  my $target_row
    = '  %5s %5s %-17s' x $target_count;    # colored text plus 11 chars
  my $header_format = "%-7s$target_header\n";
  my $row_format    = "%-7s$target_row\n";

  say 'Summary';
  printf $header_format, 'Service', $targets->@*;
  printf $row_format, '',
    map { qw|Files Tests|, 'Result' . color_info('') } 1 .. $target_count;

  for my $service (sort keys $results->%*) {
    my @row = ($service);
    for my $target (sort keys $results->{$service}->%*) {
      my $ok

        # = $results->{$service}{$target}{output} =~ /Result: PASS/
        # ? color_ok('  OK')
        # : color_error(' Error');
        = $results->{$service}{$target}{rc}
        ? color_error('Error')
        : color_ok('  OK');
      my ($files, $tests)
        = $results->{$service}{$target}{output} =~ /Files=(\d+), Tests=(\d+)/;
      push @row, $files // '', $tests // '', $ok;
    }
    printf $row_format, @row;
  }
}

sub _select_services ($config) {
  my @services;
  if ($config->{service}) {
    @services = split /,/, $config->{service};
  }
  else {
    my $compose = Load path($config->{compose_file})->slurp;
    @services = grep /$config->{service_regex}/,
      sort keys $compose->{services}->%*;
  }
  return @services;
}

sub _select_targets ($config) {
  return if $config->{execute} || !$config->{target_regex};

  my @targets;
  if ($config->{target}) {
    @targets = split /,/, $config->{target};
  }
  else {
    my $compose = Load path($config->{compose_file})->slurp;
    @targets = grep /$config->{target_regex}/,
      sort keys $compose->{services}->%*;
  }
  return @targets;
}
