#!/usr/bin/perl

use strict;
use warnings;
use YAML;
use Data::Dumper;
use JSON::MaybeXS;
use Net::Docker::Registry::Client;
use Template;
use Encode;
use URI::Escape;
use List::Util qw(first reduce);

my $cfg_file = "/etc/cooverview/config.yml";

if ($ENV{NET_SERVER_TYPE} && $ENV{NET_SERVER_TYPE} eq 'Net::Server::PreFork') {
  $cfg_file = "./config.yml";
}

my $config = YAML::LoadFile($cfg_file);
my $drc = Net::Docker::Registry::Client->new(%$config);
my $tconfig = {
  INCLUDE_PATH => $config->{templates_dir} || '/etc/cooverview/templates'
};

my $template = Template->new($tconfig);

print "Content-type:text/html\r\n\r\n";

my $predefined_search_buttons = [
  {name => "Official (default)", search_term => "project=^openSUSE:Containers: container=.*", css_class => 'success' },
  {name => "Devel (default)"   , search_term => "project=^(?!(open)?SUSE|kubic|home):')"    , css_class => 'warning' },
  {name => "Home (default)"    , search_term => "project=^home"                             , css_class => 'danger'  },
  {name => "All (default)"     , search_term => "project=.*"                                , css_class => 'primary' },
];

my $stm_internal = $predefined_search_buttons->[0]->{search_term};

my $template_default_data = {
    default_search_term => $config->{default_search_term}
      || $config->{search_buttons}->[0]->{search_term}
      || $stm_internal,
};

if ($ENV{REQUEST_METHOD} eq 'POST') {
  my $content;
  read(*STDIN, $content, $ENV{CONTENT_LENGTH}) || die "Cannot read from stdin $ENV{CONTENT_LENGTH}";
  my $data;
  eval {
    $data = decode_json($content);
  };
  if ($@) {
    $data = {};
    for my $parts (split('\?', $content)) {
      for my $part (split(/&/, $parts)) {
        my ($key, $val) = split('=', $part, 2);
        $key =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
        $val =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
        $val =~ s/\+/ /g;
        $data->{$key} = $val;
      }
    }

    create_start_page($config, $drc, $data, $template_default_data);
  } else {
    create_details_html($drc, $data->{container}, $data->{pull_command});
  }
} else {
  my %search = split(/[&=]/, $ENV{QUERY_STRING} || "");
  my $query = $search{q} || $search{srch_term};
  create_start_page(
    $config,
    $drc,
    {
      srch_term => Encode::decode('utf8', uri_unescape($query))
        || $template_default_data->{default_search_term}
    },
    $template_default_data,
  );
}

exit 0;

################################################################################
# SUBS
################################################################################
sub create_start_page {
  my ($config, $drci, $data, $template_default_data) = @_;
  my $content;
  my $repos;
  my $h_repos;
  my $error;

  eval {
    if ($config->{filter_source_projects}) {
      $h_repos = $drc->obs_info_json("")->{owners};
    } else {
      my $tmp_repos = $drc->repositories();
      $h_repos->{$_} = $_ for @{$tmp_repos};
    }
  };

  if ($@) {
    $error = "An error occured while generating repository list: $@";
  } else {
    my $tmp_repos = [];
    my $srch = {project=>'', container=>''};
    foreach my $tmp (split(/(\+|\s)/, $data->{'srch_term'})) {
      my ($field, $regex) = split(/=/, $tmp, 2);
      $srch->{$field} = $regex;
    }
    while (my ($repo, $proj) = each %{$h_repos}) {
      if ($config->{filter_source_projects}) {
	my $match_proj;
	my $match_repo;
	eval {
          $match_proj = $proj !~ $srch->{project};
	};
	if ($@) {
	   $error = "Error in project regex: $@";
	   last;
	}
	eval {
          $match_repo = $repo !~ $srch->{container};
	};
	if ($@) {
	   $error = "Error in container regex: $@";
	   last;
	}
        next if (defined($srch->{project}) && $match_proj);
        next if (defined($srch->{container}) && $match_repo);
        push(@{$tmp_repos}, $repo);
      } else {
	my $match_proj;
	eval {
          $match_proj = $proj =~ /$data->{srch_term}/;
	};
	if ($@) {
	   $error = "Error in project regex: $@";
	   last;
	}
        push(@{$tmp_repos}, $repo) if ($match_proj);
      }
    }
    @{$repos} = sort @{$tmp_repos};
  }


  my $sb = $config->{search_buttons} || $predefined_search_buttons;

  $template->process(
    'index.html.tt2',
    {
      %{$template_default_data},
      error                     => $error,
      repositories              => $repos,
      css_class                 => $config->{css_class} || 'btn-link text-decoration-none',
      theme                     => $config->{theme},
      use_obs_extended_info     => $config->{use_obs_extended_info},
      build_host                => $config->{build_host},
      search_buttons            => $sb,
      srch_term                 => $data->{srch_term} || '',
    }
  ) || die $template->error();
}

sub create_details_html {
  my ($drc, $rep, $pcommand) = @_;
  my $content;
  my $error;
  my $tags = [];
  my $info;

  my $data = {
    name => $rep,
    tags => [],
    use_obs_extended_info => $config->{use_obs_extended_info},
  };
  eval {
    if ($config->{use_obs_extended_info}) {
      $data->{info} = $drc->obs_info_json($rep);
      $data->{project} = $data->{info}->{project};
      if ($config->{build_host}) {
        $data->{project_href} = ($config->{build_host}->{proto} || 'https')."://$config->{build_host}->{host}/project/show/$data->{info}->{project}";
      }
      $data->{repository} = $data->{info}->{repository};

      my $pull_prefix;
      while (my ($tag, $tag_val) = each %{$data->{info}->{tags}}) {
	my $is_helm=0;
	foreach my $img (@{$tag_val->{images}}) {
	  $img->{id} = $img->{imageid};
	  $img->{id} =~ s/.*://;
	  $img->{id} = substr $img->{id}, 0, 12;
	  $img->{size} = 0;
	  $img->{size} += $_->{blobsize} for @{$img->{layers}};
	  $img->{size} = int($img->{size} / (1024 * 1024)) . "MB";
	  if (($img->{type}||q{}) eq 'helm') { $is_helm = 1}
	}
	my $newest = reduce { $a->{buildtime} > $b->{buildtime} ? $a : $b } @{$tag_val->{images}};
	$tag_val->{buildtime} = $newest->{buildtime};
        if ($is_helm) {
          $pull_prefix = "helm pull oci://$config->{pull_host}".(($config->{pull_port}) ? ":$config->{pull_port}/" : "/");
        } else {
          $pull_prefix = "$pcommand pull $config->{pull_host}".(($config->{pull_port}) ? ":$config->{pull_port}/" : "/");
        }

	my $tag_data = {
	  pull_command =>  "$pull_prefix$rep:$tag",
	  name         => $rep,
	  tag          => $tag,
	};
	push @{$data->{tags}}, $tag_data;
      }
      $_->{buildtime} = @{$data->{info}->{tags}}{$_->{tag}}->{buildtime} for @{$data->{tags}};
      @{$data->{tags}} = sort { $b->{buildtime} <=> $a->{buildtime} || $a->{tag} cmp $b->{tag} } @{$data->{tags}};
    } else {
      my $all_tags = $drc->list_tags($rep);
      die "No tags defined for this image\n" unless $all_tags;
      for my $tag (@{$all_tags}) {
	my $manifest = $drc->manifests($rep, $tag);
        my $pull_prefix = "$pcommand pull $config->{pull_host}".(($config->{pull_port}) ? ":$config->{pull_port}/" : "/");
	my $tag_data = {
	  pull_command => "$pull_prefix$rep:$tag",
	  manifest     => $manifest,
	  name         => $rep,
	  tag          => $tag,
	};
	push @{$data->{tags}}, $tag_data;
      }
    }
    if ($#{$data->{tags}} > 0) {
      my $latest_index = first { @{$data->{tags}}[$_]->{tag} eq 'latest' } 0..$#{$data->{tags}};
      my $latest = splice(@{$data->{tags}}, ($latest_index||0), 1);
      unshift(@{$data->{tags}}, $latest);
    }
  };

  $data->{error} = $@ if $@;
  $template->process('details.tt2', $data) || die $template->error();
}

sub logit {
  my ($msg) = @_;
  return unless $config->{logit};
  my $logfile= '/var/log/apache2/cooverview.log';
  open(my $fh, '>>', $logfile) || die "Could not open '$logfile': $!";
  $msg = (ref $msg) ? Dumper($msg) : $msg;
  print $fh $msg;
  close $fh;
}
