#!/usr/bin/env perl
# Copyright 2020 SUSE LLC
# SPDX-License-Identifier: MIT
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,
    cpanfile => \my $cpanfile,
    'specfile=s' => \my $specfile,
    'dockerfile=s' => \my $dockerfile,
);

usage(0) if $help;
usage(1) unless ($cpanfile || $specfile || $dockerfile);

my $proj_root = "$Bin/..";

my $scriptname = path(__FILE__)->to_rel($proj_root);
my $dependencies_yaml_location = 'dependencies.yaml';
my $file = "$proj_root/$dependencies_yaml_location";
my $cpanfile_location = "$proj_root/cpanfile";

my $data = YAML::PP->new->load_file($file);

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() if $specfile;
update_cpanfile($modules_by_target) if $cpanfile;
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 %seen;
    my $dep = join '', grep { not $seen{$_}++ } @pkg, @perl;
    my $begin = '# AUTODEPS START';
    my $end = '# AUTODEPS END';
    my $run = <<"EOM";
# This part is autogenerated by $scriptname from $dependencies_yaml_location
# 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)->spew($docker);
    say "Updated $dockerfile";
}

sub update_spec {
    my $spec = path($specfile)->slurp;

    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 $dependencies_yaml_location";
        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)->spew($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 $dependencies_yaml_location
##################################################

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->{develop}}) {
        $devel_requires .= '    ' . _requires_line($modules_by_target->{develop}, $module);
    }
    $cpan .= <<"EOM";

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

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

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

    path($cpanfile_location)->spew($cpan);
    say "Updated $cpanfile_location";
}

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;
}
