#!/usr/bin/perl -w

BEGIN { do '/home/mod_perl/hm/ME/FindLibs.pm'; }

use strict;
use ME::Notify qw(NotifyAdmins);
use Data::Dumper;
use Getopt::Std;
use IO::File;
use ME::CheckReplication;
use ME::ImapStore;
use ME::User;
use ME::Secure;

binmode(STDOUT, ":utf8");

our $SLEEPTIME = 2;
our $REPEATS = 3;

my %Opts;
getopts('o:dD:qb:xyCOctel:s:r:m:R:', \%Opts);

$SLEEPTIME = $Opts{s} if exists $Opts{s};
$REPEATS = $Opts{r} if exists $Opts{r};

my $Server = shift;

unless ($Server) {
  die <<EOF;
Usage: $0 [-c] [-d] [-q] [-t] [-x] [-y] [-s sleeptime] [-r repeats] [-D domain ]
       [-o logfile] [-l minlevel] [-m maxlevel] [-R replica] server [ usernames ]

  -b    = sleep seconds _b_etween users
  -c    = run reconstruct on errors
  -d    = print DEBUG messages
  -t    = print IMAP trace messages
  -q    = skip NOTIFY messages (quiet)
  -y    = run "sync_client -u" on errors
  -s    = sleep \$n seconds between retries
  -r    = retry \$n times
  -o    = write all messages to \$file
  -l    = minimum check level \$n
  -m    = maximum check level \$n
  -x    = eXtreme nuke (delete on replica and resync!)
  -R    = use this replica slot
  -D    = limit to users in this domain

  -C    = copy back with ExamineCyrus
  -O    = check if overloaded

EOF
}

my $rDbh = ME::DB->GetDbh( Name => 'mainreadonly' );

my @Clauses = "Status IN (0, 7, 8)";
# Limit to given users if specified
my $UsersClause = '';
if (@ARGV) {
  my $UsersClause = join ",", map { $rDbh->quote($_) } map { strip_at($_) }  @ARGV;
  push @Clauses, "UserName IN ($UsersClause)";
}

if ($Opts{D}) {
  push @Clauses, "Domain = " . $rDbh->quote($Opts{D});
}

my @Messages;

my $LogFile;

if ($Opts{o}) {
  $LogFile = IO::File->new($Opts{o}, 'w') || die "Can't open $Opts{o} for write";
  binmode($LogFile, ":utf8");
}

# List all "alive" users on this machine
my $Store = ME::ImapStore->new_find($Server);
die "No stores on $Server" unless $Store;
$Server = $Store->MasterHostName();
my $MasterSlot = $Store->MasterSlot();
my @OtherSlots = $MasterSlot->OtherSlots();
die "Not enough slots in " . $Store->Name() if (scalar(@OtherSlots) < 1);

my $AndClause = join (" AND ", @Clauses);

my $Users = $rDbh->selectall_arrayref(<<EOF, {}, $Server);
SELECT UserId
FROM Users
WHERE ServerName = ?
  AND $AndClause
ORDER BY UserName
EOF

foreach my $ReplicaSlot (@OtherSlots) {
  next if ($Opts{R} and not ($Opts{R} eq $ReplicaSlot->Name() or $Opts{R} eq $ReplicaSlot->Machine->DataCentre()));
  check_one($MasterSlot, $ReplicaSlot, $Users);
}

print "NOTICE: Done\n" unless $Opts{q};

exit 0;

if ($Opts{e}) {
  NotifyAdmins('email', "Replication report for $Server", join("\n", @Messages));
}

sub check_one {
  my ($MasterSlot, $ReplicaSlot, $Users) = @_;
  my ($MS, $RS) = ($MasterSlot->Name, $ReplicaSlot->Name);

  my $NumUsers = scalar(@$Users);

  print "NOTICE: Checking replication status for $NumUsers users between $MS and $RS\n" unless $Opts{q};

  my $NumDone = 0;
  my $NumErrors = 0;
  for my $Row (@$Users) {
    my $UserId = $Row->[0];
    my $User = ME::User->new( $UserId );
    my $UserName = $User->{UserName};
    my $Domain = $User->{Domain};
    my $CyrusName = $User->CyrusName();
    my $Level = 0;
    my $Rand = rand(1000);
    $Level++ if $Rand >= 900;
    $Level++ if $Rand >= 980;
    $Level++ if $Rand >= 990;
    # Doesn't do anything yet $Level++ if $Rand >= 999;
    if (exists $Opts{l} and $Level < $Opts{l}) {
      $Level = $Opts{l};
    }
    if (exists $Opts{m} and $Level > $Opts{m}) {
      $Level = $Opts{m};
    }
    while ($Opts{O} and grep { $_->Machine->IsOverloaded() } $MasterSlot, @OtherSlots) {
      if ($LogFile) {
        $LogFile->print("DEBUG: One of the servers is overloaded, sleeping 60 seconds\n");
      }
      else {
        print("DEBUG: One of the servers is overloaded, sleeping 60 seconds\n");
      }
      sleep 60;
    }
    ++$NumDone;
    my $CR = ME::CheckReplication->new(
      MasterSlot => $MasterSlot,
      ReplicaSlot => $ReplicaSlot,
      UserId => $UserId,
      UserName => $UserName,
      Domain => $Domain,
      CyrusName => $CyrusName,
      Password => ME::Secure::GetBackendPassword(),
      SleepTime => $SLEEPTIME,
      NumRepeats => $REPEATS,
      Debug => $Opts{d},
      Quiet => $Opts{q},
      LogFile => $LogFile,
      RunReconstruct => $Opts{c},
      ExtremeNuke => $Opts{x},
      TraceImap => $Opts{t},
      DoSync => $Opts{y},
      CopyBack => $Opts{C},
    );
    next unless $CR;

    $0 = "CheckReplication: $Server($RS) $UserName\@$Domain $Level ($NumDone/$NumUsers)";
    $CR->debug("Checking $Server $MS $RS $UserName\@$Domain ($Level)");
    eval { 
      $CR->CheckUserReplication($Level);
    };
    warn $@ if $@;
    push @Messages, $CR->GetMessages();
    $NumErrors++ if $CR->HasError();
  }
}

sub strip_at {
  my $fn = shift;
  $fn =~ s/\@.*//;
  return $fn;
}
