#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;

use YAML::PP;
use Data::Dumper;
use Mojo::File qw(path);
use Getopt::Long;
use FindBin qw($Bin);

GetOptions(
    "help|h" => \my $help,
    "specfile=s" => \my $specfile,
    "dockerfile=s" => \my $dockerfile,
);

usage(0) if $help;
usage(1) unless $specfile;

my $scriptname   = path(__FILE__)->to_rel("$Bin/..");
my $yamlfile     = "dependencies.yaml";
my $file         = "$Bin/../$yamlfile";
my $cpanfile     = "$Bin/../cpanfile";

my $data     = YAML::PP->new->load_file($file);
my $spec     = path($specfile)->slurp;

my $spectargets   = $data->{targets}->{spec};
my $cpantargets   = $data->{targets}->{cpanfile};
my $dockertargets = $data->{targets}->{docker};
my $cpantarget_mapping = $data->{targets}->{'cpanfile-targets'};

my ($modules_by_target) = get_modules($data, $cpantargets, $cpantarget_mapping);

update_spec();
update_cpanfile($modules_by_target);
update_dockerfile($dockerfile) if $dockerfile;

sub update_dockerfile {
    my ($dockerfile) = @_;
    my $docker = path($dockerfile)->slurp;
    my @perl;
    my @pkg;
    for my $target (@$dockertargets) {
        my $name = $target . '_requires';
        my $deps     = $data->{$name};
        for my $key (sort keys %$deps) {
            next if $key =~ m/^%/;
            my $line = '       ';

            if ($key =~ m/\(/) {
                $key = "'$key'";
            }
            $line .= $key;
            $line .= " \\\n";
            if ($key =~ m/perl\(/) {
                push @perl, $line;
            }
            else {
                push @pkg, $line;
            }
        }
    }
    @perl = sort @perl;
    @pkg = sort @pkg;
    my $dep = join '', @pkg, @perl;
    my $begin = '# AUTODEPS START';
    my $end = '# AUTODEPS END';
    my $run = <<"EOM";
# This part is autogenerated by $scriptname from $yamlfile
# hadolint ignore=DL3034,DL3037
RUN zypper in -y -C \\
$dep   && zypper clean
EOM
    $docker =~ s/($begin\n)(.*)($end\n)/$1$run$3/s;
    path($dockerfile)->spurt($docker);
    say "Updated $dockerfile";
}

sub update_spec {

    for my $target (@$spectargets) {
        my $name = $target . '_requires';
        my $deps     = $data->{$name};
        my $prefix   = "%define $name";
        my $specline = $prefix;
        for my $key (sort keys %$deps) {
            my $version = $deps->{$key};
            if (ref $version) {
                $version = $version->{rpm};
            }
            $specline .= " $key";
            if ($key eq 'perl(Perl::Tidy)') {
                undef $version;
            }
            if ($version) {
                $specline .= " $version";
            }
        }
        my $comment = "# The following line is generated from $yamlfile";
        if ($spec =~ s/^# .*generated.*\n^$prefix.*/$comment\n$specline/m) {
            next;
        }
        # No comment above the line yet
        unless ($spec =~ s/^$prefix.*/$comment\n$specline/m) {
            die "/^$prefix/ not found in $specfile";
        }
    }

    path($specfile)->spurt($spec);
    say "Updated $specfile";
}

sub get_modules {
    my ($data, $cpantargets, $cpantarget_mapping) = @_;

    my %modules_by_target;
    for my $target (@$cpantargets) {
        my $name = $target . '_requires';
        my $deps = $data->{$name};
        for my $key (keys %$deps) {
            my $module = $key;
            next unless $module =~ s/^perl\((.*)\)$/$1/;
            my $version = $deps->{$key};
            if (ref $version) {
                $version = $version->{perl};
            }
            my $cpantarget = $cpantarget_mapping->{$target} || 'main';
            $modules_by_target{$cpantarget}->{$module} = $version;
        }
    }
    return \%modules_by_target;
}

sub _requires_line {
    # requires 'Archive::Extract', '> 0.7';
    my ($hash, $module) = @_;
    my $version = $hash->{$module};
    my $line    = "requires '$module'";
    $line .= qq{, '$version'} if $version;
    $line .= ";\n";
    return $line;
}

sub update_cpanfile {
    my ($modules_by_target) = @_;
    my $cpan = <<"EOM";
##################################################
# WARNING
# This file is autogenerated by $scriptname
# from $yamlfile
##################################################

EOM
    for my $module (sort keys %{$modules_by_target->{main}}) {
        $cpan .= _requires_line($modules_by_target->{main}, $module);
    }
    my $test_requires = '';
    for my $module (sort keys %{$modules_by_target->{test}}) {
        $test_requires .= '    ' . _requires_line($modules_by_target->{test}, $module);
    }
    my $cover_requires = '';
    for my $module (sort keys %{$modules_by_target->{cover}}) {
        $cover_requires .= '    ' . _requires_line($modules_by_target->{cover}, $module);
    }
    my $devel_requires = '';
    for my $module (sort keys %{$modules_by_target->{devel}}) {
        $devel_requires .= '    ' . _requires_line($modules_by_target->{devel}, $module);
    }
    $cpan .= <<"EOM";

on 'test' => sub {
$test_requires
};

on 'devel' => sub {
$devel_requires
};

feature 'coverage', 'coverage for CI' => sub {
$cover_requires
};
EOM

    path($cpanfile)->spurt($cpan);
    say "Updated $cpanfile";
}

sub usage {
    my ($exit) = @_;
    print <<"EOM";
Usage:
    # update cpanfile and dist/rpm/os-autoinst.spec
    $0
    $0 --specfile dist/rpm/os-autoinst.spec
    $0 --dockerfile docker/ci/Dockerfile
EOM
    exit $exit;
}
