#!/usr/bin/perl

$| = 1;

use lib::abs '.';
use lib::abs '../../cgi-bin';
use lib::abs '../../admin/lib';
use lib::abs '../../perl5/lib/perl5';

use common::sense;

use Data::Dumper;
use File::Copy;
use Fcntl ':flock';
use Getopt::Long;
use IO::Handle;
use JSON;
use List::MoreUtils 'uniq';

use AdminScriptsEnvironment ':all';
use Database::Converter;

sub main {
    eval {
        my ($admconfig_path, $config_path);
        GetOptions(
            'admconfig=s' => \$admconfig_path,
            'config=s'    => \$config_path,
        );

        my ($admconfig, $config) = load_config($admconfig_path, $config_path);
        my $dbaf   = load_dbaf();

        my $tasks_path = $admconfig->GetVal('background_tasks_path');

        my $tasks = get_tasks($tasks_path);
        my $dbshard_names = get_dbshard_names($dbaf);

        for my $task (@$tasks) {
            process_task($task, $dbaf, $dbshard_names);
        }
    };

    if ($@) {
        logerror '[error] ', $@;
        exit 1;
    }
}

sub get_dbshard_names {
    my $dbaf = shift;

    my $groups = $dbaf->uid_collection->groups;
    my @slaves = map { $_->slave   } @$groups;
    my @dsns   = map { $_->get_dsn } @slaves;

    my @result = map { /database=(.*?);/ } @dsns;
    @result = sort uniq @result;

    return \@result;
}

sub get_tasks {
    my $path = shift;

    opendir my $dir, $path
      or die "can't opendir $path: $!";

    my @files = readdir $dir;

    closedir $dir;

    my @tasks;
    for my $file (@files) {
        next unless $file =~ /^(host-mailboxes-.*)\.input$/;
        push @tasks, {
            task_id        => $1,
            input_filename => $file,
            path           => $path,
        };
    }

    return \@tasks;
}

sub process_task {
    my ($task, $dbaf, $dbshard_names) = @_;

    my $input_filepath = "$task->{path}/$task->{input_filename}";
    my $log_filename   = "$task->{task_id}.log";
    my $log_filepath   = "$task->{path}/$log_filename";

    open my $log_file, '>>', $log_filepath
      or die "can't open $log_filepath: $!";

    $log_file->autoflush(1);

    $task->{log_file} = $log_file;

    open my $input_file, '<', $input_filepath
      or die "can't open $input_filepath: $!";

    $task->{input_file} = $input_file;

    unless (get_lock($input_file)) {
        logit("$input_filepath already locked");
        return 0;
    }

    eval {
        logtask($task, 'Processing');

        my $input_json = <$input_file>;
        $task->{input} = decode_json $input_json;

        my $dbshards_number = scalar @$dbshard_names;
        my $mail_hostid_attribute_type = Database::Converter->get_attribute_type_by_name('subscription.mail.host_id');

        my $central_slave = $dbaf->central_group->slave;

        my $dsn = $central_slave->get_dsn;
        $dsn =~ s/^.*?:.*?://;
        my @dsn_args = split /;/, $dsn;
        my %dsn_args = map { split /=/ } @dsn_args;

        my $dbhost = $dsn_args{hostname} || $dsn_args{host};
        my $dbport = $dsn_args{port} || 3306;
        my $dbname = $dsn_args{database};
        my $dbuser = $central_slave->user;
        my $dbpass = $central_slave->pass;

        my @mysql_base_args = ('mysql', "--host=$dbhost", "--port=$dbport", "--database=$dbname", "--user=$dbuser", "--password=$dbpass", "--skip-column-names");

        my @dump_filenames;

        for my $i (1 .. $dbshards_number) {
            my $dbshard_name = $dbshard_names->[$i - 1];
            logtask($task, "Dumping $i/$dbshards_number");

            my $dump_filename = "$task->{task_id}.dump$i";
            my $dump_filepath = "$task->{path}/$dump_filename";

            if (-e $dump_filepath) {
                unlink $dump_filepath
                  or die "can't delete $dump_filepath: $!";
            }

            my $query
              = "SELECT suid"
              . " FROM $dbshard_name.searchable_attributes"
              . " JOIN passportdbcentral.suid2 USING (uid)"
              . " WHERE type = $mail_hostid_attribute_type AND value IN (";

            my $host_ids = $task->{input}{host_ids} || $task->{input}{host_id};
            my @host_ids = split /,/, $host_ids;
            my $quoted_host_ids = join ',', map { $central_slave->dbh->quote($_) } @host_ids;

            $query .= $quoted_host_ids;

            $query .= ") AND uid ";
            $query .= $task->{input}{is_pdd} ? ">=" : "<";
            $query .= " 1130000000000000";

            my @mysql_args = (@mysql_base_args, "--execute", qq/"$query"/, "> $dump_filepath");

            system(join ' ', @mysql_args) == 0
              or die "tar @mysql_args failed: $?";

            push @dump_filenames, $dump_filename;

            logtask($task, "Dumped $i/$dbshards_number");
        }

        logtask($task, 'Packing');

        my $tmpresult_filepath = "$task->{path}/$task->{task_id}.tmpresult.tar.gz";
        my $result_filepath    = "$task->{path}/$task->{task_id}.result.tar.gz";

        if (-e $tmpresult_filepath) {
            unlink $tmpresult_filepath
              or die "can't delete $tmpresult_filepath: $!";
        }

        if (-e $result_filepath) {
            unlink $result_filepath
              or die "can't delete $result_filepath: $!";
        }

        my @tar_args = ('tar', 'czf', $tmpresult_filepath, '-C', $task->{path}, $task->{input_filename}, $log_filename, @dump_filenames);

        system(@tar_args) == 0
          or die "tar @tar_args failed: $?";

        move($tmpresult_filepath, $result_filepath)
          or die "can't move $tmpresult_filepath to $result_filepath: $!";

        for my $dump_filename (@dump_filenames) {
            unlink "$task->{path}/$dump_filename"
              or die "can't delete $dump_filename: $!";
        }

        unlink $input_filepath
          or die "can't delete $input_filepath: $!";

        logtask($task, 'Packed');
    };

    release_lock($input_file);

    if ($@) {
        logerror($@);
        logtask($task, "Errored: $@");
    }
    else {
        logtask($task, 'Finished');
    }

    return 1;
}

sub get_lock {
    my $fh = shift;
    return unless flock $fh, LOCK_EX | LOCK_NB;
    return $fh;
}

sub release_lock {
    my $fh = shift;
    flock $fh, LOCK_UN;
    close $fh;
}

sub logtask {
    my ($task, $string) = @_;
    my $log_file = $task->{log_file};
    print $log_file scalar(localtime time), " $string\n";
    logit("$task->{task_id} $string");
}

main();
