#!/usr/bin/env perl

use v5.36;
use FindBin;
use Parallel::ForkManager;
use Capture::Tiny ('capture');

my $archs = ['x86_64', 'arm64'];
my $regions = +{ map {
    my $arch = $_;
    open my $fh, '<', "$FindBin::Bin/regions-$arch.txt" or die "$!";
    my @regions = sort { $a cmp $b } map { chomp; $_; } <$fh>;
    close($fh);
    ($arch => \@regions);
} @$archs };

my $versions = [
    "5.38",
    "5.36",
    "5.34",
    "5.32",
    "5.30",
    "5.28",
    "5.26",
];
$versions = [sort {version->parse("v$b") <=> version->parse("v$a")} @$versions];

# get the list of layers on Amazon Linux 1
my $layers = {};
my $pm = Parallel::ForkManager->new(10);
$pm->run_on_finish(sub {
    my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data) = @_;
    return unless $data;
    my ($version, $region, $arn) = @$data;
    return unless $version && $region && $arn;
    $layers->{$version} //= {};
    $layers->{$version}{$region} = $arn;
});

for my $version (@$versions) {
    for my $region (@{$regions->{x86_64}}) {
        say STDERR "loading $version in $region...";
        $pm->start("$version/$region") and next;

        my $runtime_stack = "lambda-@{[ $version =~ s/[.]/-/r ]}-runtime";
        my $paws_stack = "lambda-@{[ $version =~ s/[.]/-/r ]}-paws";
        my ($stdout, $stderr, $exit);

        ($stdout, $stderr, $exit) = capture {
            system("aws --region $region cloudformation describe-stacks --output json --stack-name $runtime_stack | jq -r .Stacks[0].Outputs[0].OutputValue");
        };
        if ($exit != 0) {
            if ($stderr =~ /ValidationError/) {
                # the stack doesn't exist; skip it.
                $pm->finish;
                next;
            }
            die "failed to execute aws cli";
        }
        my $runtime_arn = $stdout;

        ($stdout, $stderr, $exit) = capture {
            system("aws --region $region cloudformation describe-stacks --output json --stack-name $paws_stack | jq -r .Stacks[0].Outputs[0].OutputValue");
        };
        if ($exit != 0) {
            if ($stderr =~ /ValidationError/) {
                # the stack doesn't exist; skip it.
                $pm->finish;
                next;
            }
            die "failed to execute aws cli";
        }
        my $paws_arn = $stdout;
 
        chomp($runtime_arn);
        chomp($paws_arn);
        $pm->finish(0, [$version, $region, {
            runtime_arn     => $runtime_arn,
            runtime_version => (split /:/, $runtime_arn)[-1],
            paws_arn        => $paws_arn,
            paws_version    => (split /:/, $paws_arn)[-1],
        }]);
    }
}
$pm->wait_all_children;

chomp(my $module_version = `cat $FindBin::Bin/../META.json | jq -r .version`);
my $latest_perl = $versions->[0];
my $latest_perl_layer = $latest_perl =~ s/[.]/-/r;
my $latest_runtime_arn = $layers->{$latest_perl}{'us-east-1'}{runtime_arn};
my $latest_runtime_version = $layers->{$latest_perl}{'us-east-1'}{runtime_version};
my $latest_paws_arn = $layers->{$latest_perl}{'us-east-1'}{paws_arn};
my $latest_paws_version = $layers->{$latest_perl}{'us-east-1'}{paws_version};

open my $fh, '>', "$FindBin::Bin/../lib/AWS/Lambda/AL.pm" or die "$!";

sub printfh :prototype($) {
    my $contents = shift;
    $contents =~ s/\@\@VERSION\@\@/$module_version/g;
    $contents =~ s/\@\@LATEST_PERL\@\@/$latest_perl/g;
    $contents =~ s/\@\@LATEST_PERL_LAYER\@\@/$latest_perl_layer/g;
    $contents =~ s/\@\@LATEST_RUNTIME_ARN\@\@/$latest_runtime_arn/g;
    $contents =~ s/\@\@LATEST_RUNTIME_VERSION\@\@/$latest_runtime_version/g;
    $contents =~ s/\@\@LATEST_PAWS_ARN\@\@/$latest_paws_arn/g;
    $contents =~ s/\@\@LATEST_PAWS_VERSION\@\@/$latest_paws_version/g;
    print $fh $contents;
}

printfh(<<'EOS');
package AWS::Lambda::AL;
use 5.026000;
use strict;
use warnings;

our $VERSION = "@@VERSION@@";

EOS

print $fh "# This list is auto generated by authors/update-aws-lambda-al.pl; DO NOT EDIT\n";
print $fh "our \$LAYERS = {\n";
for my $version (@$versions) {
    print $fh "    '$version' => {\n";
    for my $region (@{$regions->{x86_64}}) {
        next unless $layers->{$version}{$region}{runtime_arn};
        print $fh <<EOS
        '$region' => {
            runtime_arn     => "$layers->{$version}{$region}{runtime_arn}",
            runtime_version => $layers->{$version}{$region}{runtime_version},
            paws_arn        => "$layers->{$version}{$region}{paws_arn}",
            paws_version    => $layers->{$version}{$region}{paws_version},
        },
EOS
    }
    print $fh "    },\n";
}
print $fh "};\n\n";

printfh(<<'EOS');

sub get_layer_info {
    my ($version, $region) = @_;
    return $LAYERS->{$version}{$region};
}

sub print_runtime_arn {
    my ($version, $region) = @_;
    print $LAYERS->{$version}{$region}{runtime_arn};
}

sub print_paws_arn {
    my ($version, $region) = @_;
    print $LAYERS->{$version}{$region}{paws_arn};
}

1;
__END__

=encoding utf-8

=head1 NAME

AWS::Lambda::AL - AWS Lambda Custom Runtimes based on Amazon Linux

=head1 SYNOPSIS

You can get the layer ARN in your script by using C<get_layer_info>.

    use AWS::Lambda::AL;
    my $info = AWS::Lambda::AL::get_layer_info(
        "@@LATEST_PERL@@",      # Perl Version
        "us-east-1", # Region
        "x86_64",    # Architecture ("x86_64" or "arm64", optional, the default is "x86_64")
    );
    say $info->{runtime_arn};     # @@LATEST_RUNTIME_ARN@@
    say $info->{runtime_version}; # @@LATEST_RUNTIME_VERSION@@
    say $info->{paws_arn}         # @@LATEST_PAWS_ARN@@
    say $info->{paws_version}     # @@LATEST_PAWS_VERSION@@,

Or, you can use following one-liner.

    perl -MAWS::Lambda -e 'AWS::Lambda::AL::print_runtime_arn("@@LATEST_PERL@@", "us-east-1")'
    perl -MAWS::Lambda -e 'AWS::Lambda::AL::print_paws_arn("@@LATEST_PERL@@", "us-east-1")'

The list of all available layer ARN is:

=over

EOS

for my $version (@$versions) {
    print $fh "=item Perl $version\n\n=over\n\n";
    for my $region (@{$regions->{x86_64}}) {
        next unless $layers->{$version}{$region}{runtime_arn};
        print $fh "=item C<$layers->{$version}{$region}{runtime_arn}>\n\n";
    }
    print $fh "=back\n\n";
}

printfh(<<'EOS');
=back

And Paws layers:

=over

EOS

for my $version (@$versions) {
    print $fh "=item Perl $version\n\n=over\n\n";
    for my $region (@{$regions->{x86_64}}) {
        next unless $layers->{$version}{$region}{paws_arn};
        print $fh "=item C<$layers->{$version}{$region}{paws_arn}>\n\n";
    }
    print $fh "=back\n\n";
}

printfh(<<'EOS');
=back

=head2 Pre-built Zip Archives for Amazon Linux

URLs of zip archives are:

C<https://shogo82148-lambda-perl-runtime-$REGION.s3.amazonaws.com/perl-$VERSION-runtime.zip>

And Paws:

C<https://shogo82148-lambda-perl-runtime-$REGION.s3.amazonaws.com/perl-$VERSION-paws.zip>

=head2 Pre-installed modules

The following modules are pre-installed for convenience.

=over

=item L<AWS::Lambda>

=item L<AWS::XRay>

=item L<JSON>

=item L<Cpanel::JSON::XS>

=item L<JSON::MaybeXS>

=item L<YAML>

=item L<YAML::Tiny>

=item L<YAML::XS>

=item L<Net::SSLeay>

=item L<IO::Socket::SSL>

=item L<Mozilla::CA>

=back

L<Paws> is optional.

=head1 SEE ALSO

=over

=item L<AWS::Lambda>

=item L<AWS::Lambda::Bootstrap>

=item L<AWS::Lambda::Context>

=item L<AWS::Lambda::PSGI>

=item L<Paws>

=item L<AWS::XRay>

=back

=head1 LICENSE

The MIT License (MIT)

Copyright (C) ICHINOSE Shogo

=head1 AUTHOR

ICHINOSE Shogo

=cut
EOS

close($fh);
