#
# (c) Jan Gehring <jan.gehring@gmail.com>
#
# vim: set ts=2 sw=2 tw=0:
# vim: set expandtab:

package Rex::Repositorio::Repository::Yum;

use Moose;
use Try::Tiny;
use File::Basename qw'basename';
use Data::Dumper;
use Carp;
use Params::Validate qw(:all);
use File::Spec;
use File::Path 'make_path';
use IO::All;
use JSON::XS;
use Expect;

our $VERSION = '1.2.1'; # VERSION

extends "Rex::Repositorio::Repository::Base";

sub mirror {
  my ( $self, %option ) = @_;

  unless ($self->repo->{url}) {
    $self->app->logger->notice('No URL, skipping mirror');
    return
  }

  $self->repo->{url} =~ s/\/$//;
  $self->repo->{local} =~ s/\/$//;
  my $name = $self->repo->{name};

  $self->app->logger->notice("Downloading metadata...");

  my ( $packages_ref, $repomd_ref );
  ( $packages_ref, $repomd_ref ) = $self->_get_repomd_xml( $self->repo->{url} );
  my @packages = @{$packages_ref};

  my $url          = $self->repo->{url} . "/repodata/repomd.xml";
  my $destbase     = $self->app->get_repo_dir(repo => $self->repo->{name});
  my $repodatabase = File::Spec->catfile( 'repodata' );

  try {
    $self->download_metadata(
      url   => $url,
      dest  => File::Spec->catfile($repodatabase,'repomd.xml'),
      force => $option{update_metadata},
    );

    $self->app->logger->info("2/3 ${url}");

    $self->download_metadata(
      url   => $url,
      dest  => File::Spec->catfile($repodatabase,'repomd.xml'),
      force => $option{update_metadata},
    );

    $self->app->logger->info("3/3 ${url}");
  }
  catch {
    $self->app->logger->info("3/3 ${url}");
    $self->app->logger->error($_);
  };

  $self->app->logger->notice('Downloading packages...');
  $self->_download_packages( \%option, @packages );

  $self->app->logger->notice('Downloading rest of metadata...');

  my $m_count = 0;
  my $m_total = scalar(@{$repomd_ref->{data}});

  for my $file_data ( @{ $repomd_ref->{data} } ) {

    my $file_url =
      $self->{repo}->{url} . "/" . $file_data->{location}->[0]->{href};
    my $file = basename $file_data->{location}->[0]->{href};

    $m_count++;
    $self->app->logger->info("${m_count}/$m_total ${file_url}");

    $self->download_metadata(
      url  => $file_url,
      dest => File::Spec->catfile($repodatabase,$file),
      cb   => sub {
        $self->_checksum(
          @_,
          $file_data->{checksum}->[0]->{type},
          $file_data->{checksum}->[0]->{content}
        );
      },
      force => $option{update_metadata},
    );
  }

  if ( exists $self->repo->{images} && $self->repo->{images} eq "true" ) {

    $self->app->logger->notice('Downloading images...');
    my @files = (
        "images/boot.iso",           "images/efiboot.img",
        "images/efidisk.img",        "images/install.img",
        "images/pxeboot/initrd.img", "images/pxeboot/vmlinuz",
        "images/upgrade.img",        "LiveOS/squashfs.img",
    );
    my $file_count = 0;
    my $file_total = scalar @files;

    for my $file (@files) {
      my $file_url   = $self->repo->{url} . "/" . $file;
      my $local_file = File::Spec->catfile($file);

      $file_count++;
      $self->app->logger->info("${file_count}/$file_total ${file_url}");

      try {
        $self->download_package(
          url  => $file_url,
          name => $file,
          dest => $local_file,
        );
        1;
      }
      catch {
        $self->app->logger->error("Error downloading ${file_url}.");
      };
    }
  }
}

sub _download_packages {
  my ( $self, $_option, @packages ) = @_;

  if ( !$_option->{update_files} && !$_option->{force} ) {
    return;
  }

  my %option = %{$_option};

  my $p_count = 0;
  my $p_total = scalar @packages;

  my $destbase = $self->app->get_repo_dir(repo => $self->repo->{name});

  for my $package (@packages) {
    my $package_url  = $self->repo->{url} . "/" . $package->{location};
    my $package_name = $package->{name};

    $p_count++;
    $self->app->logger->info("${p_count}/$p_total ${package_url}");
    my $local_file = File::Spec->catfile($package->{location});

    my ($type, $value);
    if ($option{'checksums'}) {
      $type = $package->{checksum}->{type};
      $value = $package->{checksum}->{data};
    }
    else {
      $type = 'size';
      $value = $package->{size};
    }

    $self->download_package(
      url  => $package_url,
      name => $package_name,
      dest => $local_file,
      cb   => sub {
        $self->_checksum(
          @_,
          $type,
          $value,
        );
      },
      update_file => $option{update_files},
      force       => $option{force},
    );
  }
}

sub _get_repomd_xml {
  my ( $self, $url ) = @_;

  my $repomd_ref =
    $self->decode_xml( $self->download("${url}/repodata/repomd.xml") );

  my ($primary_file) =
    grep { $_->{type} eq "primary" } @{ $repomd_ref->{data} };
  $primary_file = $primary_file->{location}->[0]->{href};

  $url = $url . "/" . $primary_file;
  my $xml = $self->get_xml( $self->download_gzip($url) );

  my @packages;
  my @xml_packages = $xml->getElementsByTagName('package');
  for my $xml_package (@xml_packages) {
    my ($name_node)     = $xml_package->getChildrenByTagName("name");
    my ($checksum_node) = $xml_package->getChildrenByTagName("checksum");
    my ($size_node)     = $xml_package->getChildrenByTagName("size");
    my ($location_node) = $xml_package->getChildrenByTagName("location");
    push @packages, {
      location => $location_node->getAttribute("href"),
      name     => $name_node->textContent,
      checksum => {
        type => $checksum_node->getAttribute("type"),
        data => $checksum_node->textContent,
      },
      size => $size_node->getAttribute('package'),
    };
  }

  return ( \@packages, $repomd_ref );
}

sub init {
  my $self = shift;

  my $repo_dir = $self->app->get_repo_dir(repo => $self->repo->{name});
  my $repodata_path = File::Spec->catdir($repo_dir, 'repodata');
  $self->app->logger->debug("init: repodata_path: ${repodata_path}");
  unless (-d $repodata_path) {
    $self->app->logger->debug("init: make_path: ${repodata_path}");
    my $make_path_error;
    #my $dirs = File::Path->make_path($repodata_path, { error => \$make_path_error },);
    #my $dirs = File::Path->make_path($repodata_path);
    unless (make_path($repodata_path)) {
      $self->app->logger->log_and_croak(level => 'error', message => "init: unable to create path: ${repodata_path}");
    }
  }

  $self->_run_createrepo();
}

sub add_file {
  my $self   = shift;
  my %option = validate(
    @_,
    {
      file => {
        type => SCALAR
      },
    }
  );

  my $dest = File::Spec->catfile($self->app->get_repo_dir(repo => $self->repo->{name}), basename( $option{file} ));

  if ( exists $self->repo->{gpg} && $self->repo->{gpg}->{key} ) {
    my $key_id = $self->repo->{gpg}->{key};
    my $exp = Expect->spawn("/bin/rpmsign", "--addsign", "--key-id=$key_id", $option{file});
    $exp->expect(60, [
                    qr/Enter pass phrase:/ => sub {
                      my $exp = shift;
                      $exp->send($self->repo->{gpg}->{password} . "\n");
                      exp_continue;
                    },
            ]);
    $exp->soft_close();
  }

  $self->add_file_to_repo( source => $option{file}, dest => $dest );

  $self->_run_createrepo();
}

sub remove_file {
  my $self = shift;

  my %option = validate(
    @_,
    {
      file => {
        type => SCALAR
      },
    }
  );

  my $file = File::Spec->catfile($self->app->get_repo_dir(repo => $self->repo->{name}), basename( $option{file} ));

  $self->remove_file_from_repo( file => $file );

  $self->_run_createrepo();
}

sub _run_createrepo {
  my $self = shift;

  my $repo_dir = $self->app->get_repo_dir(repo => $self->repo->{name});

  if ( exists $self->repo->{gpg} && $self->repo->{gpg}->{key} ) {
    unlink File::Spec->catfile($repo_dir, qw/ repodata repomd.xml.asc /);
  }

  system "cd $repo_dir ; createrepo .";
  if ( $? != 0 ) {
    confess "Error running createrepo.";
  }

  if ( exists $self->repo->{gpg} && $self->repo->{gpg}->{key} ) {
    my $key  = $self->repo->{gpg}->{key};
    my $pass = $self->repo->{gpg}->{password};
    if ( !$pass ) {
      $pass = $self->read_password("GPG key passphrase: ");
    }

    my $cmd =
        "cd $repo_dir ; gpg --default-key $key -a --batch --passphrase '"
      . $pass
      . "' --detach-sign repodata/repomd.xml";
    system $cmd;

    if ( $? != 0 ) {
      $cmd =~ s/\Q$pass\E/\*\*\*\*\*\*\*/;
      confess "Error running: $cmd";
    }

    my $pub_file = $self->repo->{name} . ".asc";
    unlink File::Spec->catfile($repo_dir, $pub_file);
    $cmd = "cd $repo_dir ; gpg -a --output $pub_file --export $key";
    system $cmd;

    if ( $? != 0 ) {
      confess "Error running gpg export: $cmd";
    }
  }
}

# test if all necessary parameters are available
override verify_options => sub {
  my $self = shift;
  super();

  if ( !exists $self->repo->{local} ) {
    confess "No local path (local) given for: " . $self->repo->{name};
  }
};

1;
