#!/usr/bin/perl

use strict;
use warnings;
use Debian::PkgJs::Lib;
use Debian::PkgJs::Npm;
use Dpkg::IPC;
use Getopt::Std;
use JSON;
use threads;

BEGIN {
    eval '
use Devscripts::Uscan::Config;
use Devscripts::Uscan::FindFiles;
use Devscripts::Uscan::Output;
use Devscripts::Uscan::WatchFile;
';
    if ($@) {
        print STDERR "Install devscripts to use $0\n";
        exit 1;
    }
}

my ( $res, @npmthr, @gitthr, $exit, $watchLines, %opt );

getopts( 'hgdif', \%opt );
if ( $opt{h} or ( not @ARGV ) ) {
    print <<EOF;
Usage: $0 <node-modules-to-add-as-component>

node-modules-to-add-as-component: list of component to add. Version can be
specified (example: bson\@1.0.0)

Options:
 * -g: install new component with "group" at the end of debian/watch line
       (see uscan(1) for more)
 * -h: print this
 * -d: download new sources
 * -i: import downloaded sources (implies -d). Your repo must be git-clean
       before else `gbp import-orig` will fail
 * -f: remove origin tag if exists locally (before import)
EOF
    exit( $opt{h} ? 0 : 1 );
}

my $target = $opt{g} ? 'group' : 'ignore';

# Import implies download
$opt{d} ||= $opt{i};

my ( %wanted_versions, %after_v );

foreach my $cmp (@ARGV) {
    if ( $cmp =~ s/\@(\d[\d\.]*.*)$// ) {
        my $v = $1;
        if ( $v =~ /^0/ ) {
            $v =~ s/^(0\.\d+).*$/$1/;
        }
        else {
            $v =~ s/^(\d+).*$/$1/;
        }
        $wanted_versions{$cmp} = $v;
    }
    push @npmthr, threads->create( sub { npmrepo($cmp) } );
}

my @comp = @ARGV;

($watchLines) = getWatchLines();
$_->parse foreach (@$watchLines);
my @cmpnames = map { $_->component ? $_->component : () } @$watchLines;

foreach my $cmp (@comp) {
    my $thr = shift @npmthr;
    my ( $latest, $tmp, $vs ) = $thr->join();
    if ( $wanted_versions{$cmp} and @$vs ) {
      F: for ( my $i = $#$vs ; $i >= 0 ; $i-- ) {
            if (    $vs->[ $i + 1 ]
                and $vs->[$i] =~ /^v?\Q$wanted_versions{$cmp}\E/ )
            {
                $after_v{$cmp} = $vs->[ $i + 1 ];
                last F;
            }
        }
    }
    my $cmpname = $cmp;
    $cmpname =~ s/[^a-zA-Z0-9\-]//g;
    if ( grep { $cmpname eq $_ } @cmpnames ) {
        print STDERR "Component $cmpname already exists, skipping\n";
        $exit++;
        push @gitthr, threads->create( sub { undef } );
        next;
    }
    $tmp or die "Component $cmp not found in npm registry";
    my $repo = eval { url_part($tmp) };
    if ($@) {
        print STDERR "Unable to add $cmp: unable to parse $tmp\n";
        $exit++;
        push @gitthr, threads->create( sub { undef } );
    }
    $res->{$cmp}->{npmrepo}   = $repo;
    $res->{$cmp}->{npmlatest} = $latest;
    push @gitthr, threads->create( sub { git_last_version($repo) } );
}

my @component_added;
foreach my $cmp (@comp) {
    my $thr = shift @gitthr;
    my ($gitlatest) = $thr->join();
    unless ($gitlatest) {
        print STDERR "Unable to add $cmp\n";
        $exit++;
        next;
    }
    my $cmpname = $cmp;
    $cmpname =~ s/[^\w\-]//g;
    my $check = ( Dpkg::Version->new( "$gitlatest-0", check => 0 )
          <=> Dpkg::Version->new( "$res->{$cmp}->{npmlatest}-0", check => 0 ) );
    my $cmp_watch = "\n"
      . (
        $check
        ? registry_watch(
            $res->{$cmp}->{npmrepo}, $cmpname,
            $target,                 $wanted_versions{$cmp}
          )
        : git_watch(
            $res->{$cmp}->{npmrepo}, $cmpname,
            $target,                 $wanted_versions{$cmp},
            $after_v{$cmp},
        )
      );
    my $f;
    open $f, '>>', 'debian/watch' or die $!;
    print $f $cmp_watch;
    close $f;
    push @component_added, $cmpname;
}

eval { require Config::IniFiles };
if ($@) {
    print STDERR "Missing libconfig-inifiles-perl, skipping gbp.conf update";
    exit 1;
}

if (@component_added) {
    if ( $opt{g}
        and not( $watchLines->[0]->type and $watchLines->[0]->type eq 'group' )
      )
    {
        print STDERR
qq'Main debian/watch seems not tagged as "group", you should verify this\n';
        $exit++;
    }
    unless ( -e 'debian/gbp.conf' ) {
        my $f;
        open $f, '>', 'debian/gbp.conf' or die $!;
        print $f <<EOF;
[DEFAULT]
pristine-tar = True

[import-orig]
filter = [ '.gitignore', '.travis.yml', '.git*' ]
EOF
    }
    my $cfg = Config::IniFiles->new( -file => 'debian/gbp.conf' );
    $cfg->AddSection('DEFAULT');
    my $val = $cfg->val( 'DEFAULT', 'component' ) || '';
    my @cmp = ( $val =~ /([\w\-\.]+)/g );
    $cfg->delval( 'DEFAULT', 'component' );
    $cfg->newval( 'DEFAULT', 'component',
        '[' . join( ', ', map { "'$_'" } ( @cmp, @component_added ) ) . ']' );
    $cfg->WriteConfig('debian/gbp.conf');
    print STDERR "Components added: " . join( ', ', @component_added ) . "\n";

    if ( $opt{d} ) {

        my ( $wl, $watchFile ) = getWatchLines();
        # Workaround uscan bug
        'q' =~ /(a)?/;
        if ( $watchFile->process_lines ) {
            $exit++;
        }
        elsif ( $opt{i} ) {
            my $new_version = $dehs_tags->{'upstream-version'};
            my $mangle_v    = $new_version;
            $mangle_v =~ s/~/_/g;
            my ($out);
            spawn(
                exec       => [ 'git', 'tag' ],
                to_string  => \$out,
                wait_child => 1,
                nocheck    => 1
            );
            if ( $out =~ m#\b\Qupstream/$mangle_v\E\b# ) {
                spawn(
                    exec       => [ 'git', 'tag', '-d', "upstream/$mangle_v" ],
                    wait_child => 1
                );
                if ( $opt{f} ) {
                    spawn(
                        exec => [
                            'git',    'push',
                            'origin', '-d',
                            "upstream/$mangle_v"
                        ],
                        wait_child => 1
                    );
                }
            }
            my $filename = $wl->[0]->destfile;
            unless ( $filename =~ m#^(.*/)(.*)_(?:\d.*?)\.orig\.(.*)$# ) {
                die "$0 is unable to parse $filename, please report this bug";
            }
            my ( $path, $name, $ext ) = ( $1, $2, $3 );
            $filename = "$path${name}_$new_version.orig.$ext";
            unless ( -e $filename ) {
                die
qq'Unable to import "$path$name-$new_version.orig.$ext": file does not exist';
            }
            spawn(
                exec => [
                    'git', 'commit', '-m',
                    'Embed components: ' . join( ', ', @component_added ),
                    'debian/watch', 'debian/gbp.conf'
                ],
                wait_child => 1
            );
            exec 'gbp', 'import-orig', '--pristine-tar', $filename;
        }
    }
}

exit $exit if $exit;

sub getWatchLines {
    local @ARGV = ('-dd');
    my $config = Devscripts::Uscan::Config->new->parse;
    my @wf     = find_watch_files($config);
    my ( $pkg_dir, $package, $version, $watchfile ) = @{ $wf[0] };
    chdir $pkg_dir;
    my $watchFile = Devscripts::Uscan::WatchFile->new(
        {
            config      => $config,
            package     => $package,
            pkg_dir     => $pkg_dir,
            pkg_version => $version,
            watchfile   => $watchfile,
        }
    );
    die "Uscan initialization failed" if $watchFile->status;
    my $watchLines = $watchFile->watchlines;
    return ( $watchLines, $watchFile );
}
