#!/usr/bin/env perl

use strict;
use warnings;

use Parse::CSV;
use Getopt::Long;
use Unicode::Normalize;
use Data::Dumper;
binmode STDOUT, ':encoding(UTF-8)';
binmode STDERR, ':encoding(UTF-8)';

# default options
my $discourse_users_file = "discourse-users.csv";
my $jira_users_file = "jira-users.csv";
my $discourse_to_jira_new;
my $jira_to_discourse_new;
my $jira_to_discourse_changes;
my $overwrite;
my $merge_groups = 1;
my $use_best_name = 1;
my $use_dev_username = 1;
my $noout;
my $dev_group = "jira-developers";
my $DEBUG;

GetOptions (
  "discourse-users-file=s" => \$discourse_users_file,
  "jira-users-file=s" => \$jira_users_file,
  "discourse-to-jira-new" => \$discourse_to_jira_new,
  "jira-to-discourse-new" => \$jira_to_discourse_new,
  "jira-to-discourse-changes" => \$jira_to_discourse_changes,
  "overwrite" => \$overwrite,
  "merge-groups" => \$merge_groups,
  "use-best-name" => \$use_best_name,
  "use-dev-username" => \$use_dev_username,
  "dev-group=s" => \$dev_group,
  "noout" => \$noout,
  "debug" => \$DEBUG,
);

my $names = {};
my $discourse_users_hash = {};
my $discourse_only_hash = {};
my $discourse_users = Parse::CSV->new(
  file => $discourse_users_file,
  names => 1,
);
while( my $rec = $discourse_users->fetch() ) {
  my $email = $rec->{"email"};
  $discourse_users_hash->{$email} = $rec;
  $discourse_only_hash->{$email} = $rec;

  $names->{$email} = [] unless ref($names->{$email}) eq "ARRAY";
  push(@{$names->{$email}}, $rec->{"name"});
}

my $jira_users_hash = {};
my $jira_only_hash = {};
my $both_users_hash = {};
my $jira_users = Parse::CSV->new(
  file => $jira_users_file,
  names => 1,
);
my $lastrec;
while( my $rec = $jira_users->fetch() ) {
  # deal with the weird spreadsheet output of new row for extra groups for the previous user. "extra group name" has to be inserted as it's otherwise a blank field.
  # field line for jira users should be
  # full name,username,email,login details,extra group name,group name,blank1,applications,blank2,directory,Actions
  if ($lastrec && (length($rec->{"email"}) == length($rec->{"full name"}) == length($rec->{"username"}) == 0) && length($rec->{"extra group name"}) > 0) {
    warn("Adding '".$rec->{"extra group name"}."' to '".$lastrec->{"group name"}."' for user '$lastrec->{username}'\n") if $DEBUG;
    $lastrec->{"group name"} = merge_groups($lastrec->{"group name"}, $rec->{"extra group name"});
    next;
  }
  $lastrec = $rec;

  my $email = $rec->{"email"};

  $names->{$email} = [] unless ref($names->{$email}) eq "ARRAY";
  push(@{$names->{$email}}, $rec->{"full name"});

  if (exists $jira_users_hash->{$email}) {
    my $m_groups;
    my $dev_username;
    my $oldrec = $jira_users_hash->{$email};
    if ($merge_groups) {
      $m_groups = merge_groups($oldrec->{"group name"}, $rec->{"group name"});
      if ($use_dev_username) {
        $dev_username = $rec->{"username"} if ($rec->{"group name"} =~ m/\b${dev_group}\b/);
        $dev_username = $oldrec->{"username"} if ($oldrec->{"group name"} =~ m/\b${dev_group}\b/ && !$dev_username);
      }
      warn("Merging groups for '${email}' to '${m_groups}'\n") if $DEBUG;
    }
    if ($overwrite) {
      warn("About to overwrite '${email}'\n") if $DEBUG;
      if ($merge_groups) {
        $rec->{"group name"} = $m_groups;
        if ($use_dev_username && $dev_username) {
          $rec->{"username"} = $dev_username;
        }
      }
    } else {
      warn("About to skip duplicate email '${email}'\n") if $DEBUG;
      if ($merge_groups) {
        $oldrec->{"group name"} = $m_groups;
        if ($use_dev_username && $dev_username) {
          $oldrec->{"username"} = $dev_username;
        }
      }
      next;
    }
  }
  $jira_users_hash->{$email} = $rec;

  if (exists($discourse_users_hash->{$email})) {
    $both_users_hash->{$email} = {
      email => $email,
      jira => $jira_users_hash->{$email},
      discourse => $discourse_users_hash->{$email},
    };
    delete $discourse_only_hash->{$email};
  } else {
    $jira_only_hash->{$email} = $rec;
  }
}

#warn Dumper($jira_users_hash->{'d.m.a.martin@dundee.ac.uk'});exit;
exit if $noout;

if ($jira_to_discourse_changes || $jira_to_discourse_new || $discourse_to_jira_new) {
  print "discourse_id,email,name,username,dev\n";
}

my $done = {};

if ($jira_to_discourse_changes) {
  for my $email (sort keys %$both_users_hash) {
    next unless $email;
    if (exists($done->{$email})) {
      warn("Apparently already done '$email'\n");
      next;
    }
    my $d = $both_users_hash->{$email}{discourse};
    my $j = $both_users_hash->{$email}{jira};

    my @groups = split(m/,/, $j->{"group name"}||"");
    my $dev = grep(m/$dev_group/,@groups)?1:0;

    my $name = $use_best_name ? best_name(@{$names->{$email}}, $j->{"username"}, $d->{"username"}) : ( $j->{"name"} || $j->{"username"} );
#warn("NAMES: ".join(",",@{$names->{$email}})."\n");

    my $discourse_id = $d->{id};

    my $username = $j->{"username"} eq "invaliduser" ? $d->{"username"} : ( $j->{"username"} || $d->{"username"} );
    warn("Changing '$d->{username}' to '$j->{username}'\n") if ($d->{"username"} ne $j->{"username"} && $DEBUG);

    my $csv_line = qq(${discourse_id},${email},"${name}",${username},$dev);
    print $csv_line."\n";

    $done->{$email} = $csv_line;
  }
}

if ($jira_to_discourse_new) {
  for my $email (sort keys %$jira_only_hash) {
    next unless $email;
    if (exists($done->{$email})) {
      warn("Apparently already done '$email'\n");
      next;
    }
    my $j = $jira_only_hash->{$email};

    next if defined($j->{"name"}) and $j->{"name"} eq "Spam User";
    next if defined($j->{"email"}) and $j->{"email"} =~ m/^\(Inactive\) :: /;

    my @groups = split(m/,/, $j->{"group name"}||"");
    my $dev = grep(m/$dev_group/,@groups)?1:0;

    my $name = $use_best_name ? best_name(@{$names->{$email}}, $j->{"username"}) : $j->{"name"};

    my $username = $j->{"username"};

    my $csv_line = qq(,${email},"${name}",${username},$dev);
    print $csv_line."\n";

    $done->{$email} = $csv_line;
  }
}

if ($discourse_to_jira_new) {
  for my $email (sort keys %$discourse_only_hash) {
    next unless $email;
    if (exists($done->{$email})) {
      warn("Apparently already done '$email'\n");
      next;
    }
    my $d = $discourse_only_hash->{$email};

    my $name = $d->{"name"} ? $d->{"name"} : $d->{"username"};

    my $discourse_id = $d->{id};

    my $username = $d->{"username"};

    my $csv_line = qq(${discourse_id},${email},"${name}",${username});
    print $csv_line."\n";

    $done->{$email} = $csv_line;
  }
}

sub merge_groups {
  my ($groups1, $groups2) = @_;
  my @g1 = split(m/,/, $groups1);
  my @g2 = split(m/,/, $groups2);
  my %h;
  @h{@g1} = (1) x @g1;
  @h{@g2} = (1) x @g2;
  my $groups = join(",",sort keys %h);
  return $groups;
}

sub name_score {
  my ($name) = @_;
  return -100 if $name eq "invalid";
  return -100 if $name eq "invaliduser";
  return -100 if not defined $name;
  my $score = 0;
  $score += $name =~ m/[[:alpha:]]\s[[:alpha:]]/ ? 35 : 0; # two words
  $score += $name =~ m/[A-Z].*[a-z]/ ? 20 : 0; # capital and small letters (in that order)
  $score += $name =~ m/^[A-Z]/ ? 10 : 0; # starts with a capital letter
  $score += ($name ne NFD($name)) ? 2 : 0; # contains a diacritic
  $score += $name =~ m/\d/ ? -3 : 0; # contains a number
  $score += $name =~ m/\w\s+\w\s+\w/ ? -4 : 0;# contains a diacritic
  $score += length($name);
  return $score;
}

sub sort_name {
  return ( sort {name_score($b) <=> name_score($a)} @_ );
}

sub best_name {
  my $best_name;
  my $best_score;
  for my $name (@_) {
    my $score = name_score($name);
    if (not defined($best_score)) {
      $best_name = $name;
      $best_score = $score;
    } elsif ($score > $best_score) {
      $best_name = $name;
      $best_score = $score;
    }
  }
  warn("Choosing name '$best_name' out of '".join("', '",@_)."'\n") if $DEBUG;
  return $best_name;
}
