#!/usr/bin/perl -w
#
# Copyright 2018, Bas Couwenberg <sebastic@debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

use strict;
use Data::Dumper;
use File::Basename;
use Getopt::Long qw(:config bundling no_ignore_case);
use HTTP::Request::Common;
use JSON;
use LWP::UserAgent;
use URI::Escape;

$|=1;

my %cfg = (
            url              => 'https://salsa.debian.org/api/v4',
	    token            => '',
	    token_file       => $ENV{HOME}.'/.salsa-token',
            namespace        => '2052',
            user             => '',
            group            => 'debian-gis-team',
            ci_cfg_path      => 'debian/.gitlab-ci.yml',
            email_recipients => 'pkg-grass-devel@lists.alioth.debian.org',
            irc_recipients   => '#debian-gis',
            project          => '',
            description      => '',
            visibility       => 'public',
            debug            => 0,
            verbose          => 0,
            help             => 0,
          );

my $result = GetOptions(
                         'u|url=s'              => \$cfg{url},
                         'n|namespace=i'        => \$cfg{namespace},
                         'U|user=s'             => \$cfg{user},
                         'G|group=s'            => \$cfg{group},
                         't|token=s'            => \$cfg{token},
                         'c|ci-cfg-path=s'      => \$cfg{ci_cfg_path},
                         'e|email-recipients=s' => \$cfg{email_recipients},
                         'i|irc-recipients=s'   => \$cfg{irc_recipients},
                         'p|project=s'          => \$cfg{project},
                         'D|description=s'      => \$cfg{description},
                         'V|visibility=s'       => \$cfg{visibility},
                         'd|debug'              => \$cfg{debug},
                         'v|verbose'            => \$cfg{verbose},
                         'h|help'               => \$cfg{help},
                       );

if(!$result || $cfg{help} || !$cfg{project}) {
	print STDERR "\n" if(!$result);

	print "Usage: ". basename($0) ." -p <NAME> [OPTIONS]\n\n";
	print "Options:\n";
	print "-u, --url <URL>                 Salsa URL          ($cfg{url})\n";
	print "-t, --token <STRING>            Salsa token        (". '*' x length($cfg{token}) .")\n";
	print "-T, --token-file <PATH>         Salsa token file   ($cfg{token_file})\n";
	print "\n";
	print "-n, --namespace <ID>            Salsa namespace    ($cfg{namespace})\n";
	print "-U, --user <NAME>               Salsa user path    ($cfg{user})\n";
	print "-G, --group <NAME>              Salsa group path   ($cfg{group})\n";
	print "\n";
	print "-c, --ci-cfg-path <PATH>        CI Config Path     ($cfg{ci_cfg_path})\n";
	print "-e, --email-recipients <EMAIL>  Email recipients   ($cfg{email_recipients})\n";
	print "-i, --irc-recipients <CHANNEL>  IRC recipients     ($cfg{irc_recipients})\n";
	print "\n";
	print "-p, --project <NAME>            Project name to create\n";
	print "-D, --description <STRING>      Project description\n";
	print "-V, --visibility <STRING>       Project visibility ($cfg{visibility})\n";
	print "\n";
	print "-d, --debug                     Enable debug output\n";
	print "-v, --verbose                   Enable verbose output\n";
	print "-h, --help                      Display this usage information\n";

        exit 1;
}

if($cfg{visibility} ne 'public' &&
   $cfg{visibility} ne 'internal' &&
   $cfg{visibility} ne 'private'
) {
	print "Error: Invalid visibility value: $cfg{visibility}\n";
	print "Valid options: public, internal, private\n";

	exit 1;
}

$cfg{description} = "Packaging of $cfg{project}" if(!$cfg{description});

my $ua = new LWP::UserAgent(agent => basename($0));

$cfg{token} = get_private_token() if(!$cfg{token});

if(!$cfg{token}) {
	print "Error: No private token specified with file nor option!\n";

	exit 1;
}

$cfg{namespace} = get_namespace() if(!$cfg{namespace});

if(!$cfg{namespace}) {
	print "Error: No namespace found! Specify namespace, user or group!\n";

	exit 1;
}

# Get projects for team
my $team_projects = get_team_projects();

print "team projects:\n".Dumper($team_projects) if($cfg{debug});

# Check project existence
if(project_exists($team_projects, $cfg{project})) {
	print "Error: Project already exists: $cfg{project}\n";

	exit 1;
}

# Create project
my $project = create_project();
if(!$project) {
	exit 1;
}

# Configure Emails on push service
if(!configure_emails_on_push_service($project)) {
	exit 1;
}

# Configure Irker service
if(!configure_irker_service($project)) {
	exit 1;
}

exit 0;

################################################################################
# Subroutines

sub get_private_token {
	my $token = '';

	if(!$cfg{token_file}) {
		print "No private token file to check.\n" if($cfg{verbose});

		return $token;
	}

	print "Checking file for private token: $cfg{token_file}\n" if($cfg{debug});

	if(-r $cfg{token_file}) {
		my $mode = sprintf("%o", (stat($cfg{token_file}))[2] & 07777);
		if($mode != 600) {
			print "Warning: Permissions on $cfg{token_file} are not 600 (-rw-------)\n";
		}

		print "Loading private token from: $cfg{token_file} ($mode)\n" if($cfg{debug});

		open(F, $cfg{token_file}) || die "Error: Failed to open file: $cfg{token_file} ($!)";
		while(<F>) {
			next if(/^\s*#|^\s*$/);

			if(/^(\S{10,})\s*/) {
				$token = $1;
				last;
			}
		}
		close F;
	}
	else {
		print "File not readable: $cfg{token_file}\n" if($cfg{debug});
	}

	return $token;
}

sub get_namespace {
	my $url = $cfg{url}.'/namespaces';

	print "Getting namespaces\n" if($cfg{verbose});

	my $req = new HTTP::Request(GET => $url);
	   $req->header('PRIVATE-TOKEN' => $cfg{token});

	my $res = $ua->request($req);
	if($res->is_success) {
		my $content = $res->content;

		my $data = decode_json($content);

		print "namespaces:\n".Dumper($data) if($cfg{debug});

		my $namespace = '';

		foreach my $ns (@{$data}) {
			if($cfg{user} && $ns->{kind} eq 'user' && $ns->{path} eq $cfg{user}) {
				print "Found namespace: ". $ns->{id} ." for user: $cfg{user}\n" if($cfg{verbose});

				$namespace = $ns->{id};

				last;
			}
			elsif($cfg{group} && $ns->{kind} eq 'group' && $ns->{path} eq $cfg{group}) {
				print "Found namespace: ". $ns->{id} ." for group: $cfg{group}\n" if($cfg{verbose});

				$namespace = $ns->{id};

				last;
			}
		}

		return $namespace;
	}
	else {
		print "Error: Request failed! ($url)\n";
		print "HTTP Status: ".$res->code." ".$res->message."\n";
		print $res->content if($res->content);
	
		exit 1;
	}
}

sub get_team_projects {
	my ($projects, $page) = @_;

	$projects = [] if(!$projects);
	$page     = 1  if(!$page);

	my $url  = $cfg{url}.'/groups/'.uri_escape($cfg{namespace}).'/projects';
	   $url .= '?order_by=name';
	   $url .= '&sort=asc';
	   $url .= '&page='.uri_escape($page);
	   $url .= '&per_page=100';

	print "Getting projects for namespace: $cfg{namespace} (page: $page)\n" if($cfg{verbose});

	my $req = new HTTP::Request(GET => $url);
	   $req->header('PRIVATE-TOKEN' => $cfg{token});

	my $res = $ua->request($req);
	if($res->is_success) {
		my $content = $res->content;

		my $data = decode_json($content);

		foreach my $project (@{$data}) {
			push @{$projects}, $project;
		}

		my $next_page = $res->header('X-Next-Page');
		if($next_page && $next_page ne $page) {
			$projects = get_team_projects($projects, $next_page);
		}

		return $projects;
	}
	else {
		print "Error: Request failed! ($url)\n";
		print "HTTP Status: ".$res->code." ".$res->message."\n";
		print $res->content if($res->content);
	
		exit 1;
	}
}

sub project_exists {
	my ($projects, $name) = @_;

	foreach my $project (@{$team_projects}) {
		if(lc($project->{name}) eq lc($name) || lc($project->{path}) eq lc($name)) {
			return 1;
		}
	}

	return;
}

sub create_project {
	print "Creating project: $cfg{project}\n" if($cfg{verbose});

	my $url = $cfg{url}.'/projects';

	my %param = (
		      name                                => $cfg{project},
		      namespace_id                        => $cfg{namespace},
		      description                         => $cfg{description},
		      visibility                          => $cfg{visibility},
		      ci_config_path                      => $cfg{ci_cfg_path},
		      issues_enabled                      => 'false',
	              printing_merge_request_link_enabled => 'false',
		    );

	print "param:\n".Dumper(\%param) if($cfg{debug});

	my $req = HTTP::Request::Common::POST($url, [ %param ]);
	   $req->header('PRIVATE-TOKEN' => $cfg{token});

	my $res = $ua->request($req);
	if($res->is_success) {
		my $content = $res->content;

		print "Content:\n".Dumper($content) if($cfg{debug});

		return decode_json($content);
	}
	else {
		print "Error: Request failed! ($url)\n";
		print "HTTP Status: ".$res->code." ".$res->message."\n";
		print $res->content if($res->content);

		return;
	}
}

sub configure_emails_on_push_service {
	my ($project) = @_;

	my $url = $cfg{url}.'/projects/'.uri_escape($project->{id}).'/services/emails-on-push';

	my $req = new HTTP::Request(GET => $url);
	   $req->header('PRIVATE-TOKEN' => $cfg{token});

	my $res = $ua->request($req);
	if($res->is_success) {
		my $content = $res->content;

		my $data = decode_json($content);

		print "Emails on push:\n". Dumper($data) if($cfg{debug});

		if(!$data->{active}) {
			print "Activating Emails on push for: ". $project->{name} ." (". $project->{id} .")\n" if($cfg{verbose});

			my $url = $cfg{url}.'/projects/'.uri_escape($project->{id}).'/services/emails-on-push';

			my %param = (
			              recipients                => $cfg{email_recipients},
			              disable_diffs             => 'false',
			              send_from_committer_email => 'false',
			            );

			print "param:\n".Dumper(\%param) if($cfg{debug});

			my $req = HTTP::Request::Common::PUT($url, [ %param ]);
			   $req->header('PRIVATE-TOKEN' => $cfg{token});

			my $res = $ua->request($req);
			if($res->is_success) {
				my $content = $res->content;

				print "Content:\n".Dumper($content) if($cfg{debug});

				print "Activated Emails on push service for project: ". $project->{name} ." (". $project->{id} .")\n" if($cfg{verbose});

				return 1;
			}
			else {
				print "Error: Request failed! ($url)\n";
				print "HTTP Status: ".$res->code." ".$res->message."\n";
				print $res->content if($res->content);

				return;
			}
		}
		else {
			print "Emails on push service already active for project: ". $project->{name} ." (". $project->{id} .")\n" if($cfg{verbose});

			return 2;
		}
	}
	else {
		print "Error: Request failed! ($url)\n";
		print "HTTP Status: ".$res->code." ".$res->message."\n";
		print $res->content if($res->content);

		return;
	}
}

sub configure_irker_service {
	my ($project) = @_;

	my $url = $cfg{url}.'/projects/'.uri_escape($project->{id}).'/services/irker';

	my $req = new HTTP::Request(GET => $url);
	   $req->header('PRIVATE-TOKEN' => $cfg{token});

	my $res = $ua->request($req);
	if($res->is_success) {
		my $content = $res->content;

		my $data = decode_json($content);

		print "Irker:\n". Dumper($data) if($cfg{debug});

		if(!$data->{active}) {
			print "Activating Irker for: ". $project->{name} ." (". $project->{id} .")\n" if($cfg{verbose});

			my $url = $cfg{url}.'/projects/'.uri_escape($project->{id}).'/services/irker';

			my %param = (
			              recipients        => $cfg{irc_recipients},
			              colorize_messages => 'true',
			            );

			print "param:\n".Dumper(\%param) if($cfg{debug});

			my $req = HTTP::Request::Common::PUT($url, [ %param ]);
			   $req->header('PRIVATE-TOKEN' => $cfg{token});

			my $res = $ua->request($req);
			if($res->is_success) {
				my $content = $res->content;

				print "Content:\n".Dumper($content) if($cfg{debug});

				print "Activated Irker service for project: ". $project->{name} ." (". $project->{id} .")\n" if($cfg{verbose});

				return 1;
			}
			else {
				print "Error: Request failed! ($url)\n";
				print "HTTP Status: ".$res->code." ".$res->message."\n";
				print $res->content if($res->content);

				return;
			}
		}
		else {
			print "Irker service already active for project: ". $project->{name} ." (". $project->{id} .")\n" if($cfg{verbose});

			return 2;
		}
	}
	else {
		print "Error: Request failed! ($url)\n";
		print "HTTP Status: ".$res->code." ".$res->message."\n";
		print $res->content if($res->content);

		return;
	}
}

