#!/usr/bin/perl

use strict;
use warnings;
use open ':std', ':encoding(UTF-8)';

use Sys::Hostname;
use File::Basename;
use File::Find::Rule;
use Capture::Tiny ':all';
use Parallel::ForkManager;
use Digest::MD5::File(qw/file_md5_hex/);

use lib '/app/tools/common/lib';
use Common::Session;
use Common::Amazon::Mail;

use lib '/app/tools/admin/lib';
use Admin::BMan;

my ( $opt, $usage ) = clopt(
    [ 'backupdir|b=s' => 'Backup directory default in config yaml' ],
    [ 'full|f'        => 'Full backup of the database' ],
    [ 'force|F'       => 'Force backup if another already exists' ],
    [ 'dryrun|d'      => 'Dryrun of the backup - STDOUT only' ],
    [ 'local|l'       => 'Run backups from local db host' ],
    [ 'nocompress'    => 'Don\'t compress the sql output files' ],
    [ 'parallel|p=i'  => 'Parallel backups default: 0', { 'default' => 0 } ],
    [ 'split'         => 'Split the >5GB sql output files' ],
    [ 'schema|s=s'    => 'Schema to backup' ],
    [ 'verbose|v'     => 'Verbose' ],
    [ 'vverbose|V'    => 'Very verbose' ],
    [ 'help|?|h'      => "print usage message and exit" ]
);

if ( $opt->help ) {
    logMessage( 'info', $usage->text, 'options' );
    exit 1;
}

$opt->{'verbose'} = 1                  if $opt->dryrun or $opt->vverbose;
$opt->{'schema'}  = uc( $opt->schema ) if defined $opt->schema;

my $bman = Admin::BMan->new($opt);
eval {
    my $conf = $bman->config;
    my $bdir = $opt->backupdir || $conf->backupdir;
    my $dbx  = Common::Session::getdbx('admindb');
    my $databases;
    if ( $opt->local ) {
        my $dbh  = Common::Session::getdbh('localhost');
        my $rset = $dbx->resultset('Report::DDb')->search( { 'db_backup' => 1 }, { 'order_by' => 'db_name' } );
        if ( $rset->count ) {
            while ( my $row = $rset->next ) {
                if ( $dbh->do( "SHOW DATABASES LIKE '" . $row->db_name . "'" ) == 1 ) {
                    push @{$databases}, $row;
                } else {
                    logMessage( 'error', $row->db_name . " not on this server" );
                }
            }
        }
    } else {
        my $awshost = $dbx->resultset('Report::DDbHost')->search_related( 'aws_host', { 'hostname' => $conf->host } )->first;
        my $host    = $awshost->db_host;
        $databases = [ $host->databases ];
    }

    my $pm   = Parallel::ForkManager->new( $opt->parallel );
    my $type = $opt->full ? 'full' : 'daily';

    foreach my $db ( @{$databases} ) {
        next if not $db->db_backup;
        $pm->start and next;

        my $dt = DateTime->now;
        $dt->set_time_zone('America/Los_Angeles');
        my $date = $dt->ymd('');

        my $mysql_datetime = $dt->datetime;
        $mysql_datetime =~ s/T/ /;

        my $schema = $db->db_name;
        if ( $opt->schema ) {
            if ( not $schema eq $opt->schema ) {
                $bman->logtext( "Skipping $schema...", "warn" );
                next;
            }
        }

        my $dest_dir  = join '/', ( $bdir, $type, $date, $schema );
        my $dest_file = join '_', ( "mysqldump", $schema, $date );

        if ( -e $dest_dir ) {
            if ( $opt->force ) {
                $bman->execute("rm -rf $dest_dir");
                $bman->execute("mkdir -p $dest_dir");
            } else {
                $bman->logtext( "Files already exist in $dest_dir - use --force to remove them", "error" );
                next;
            }
        } else {
            $bman->execute("mkdir -p $dest_dir");
        }

        my $log_data = {
            'd_db_id'          => $db->d_db_id,
            'schema_filename'  => "${dest_file}.schema.sql",
            'data_filename'    => "${dest_file}.data.sql",
            'backup_directory' => $dest_dir,
            'backup_datetime'  => $mysql_datetime,
        };

        my $schema_file = "$dest_dir/$log_data->{'schema_filename'}";
        my $data_file   = "$dest_dir/$log_data->{'data_filename'}";

        my $start_data_dump = time;
        $bman->logtext("Backing up $schema schema create");
        $bman->execute( $conf->exec_schema . " $schema > $schema_file" );

        my $ignore = join ' ', map { "--ignore-table=$schema.$_" } @{ $conf->ignore_tables };
        $bman->logtext("Backing up $schema schema data");
        $bman->execute( $conf->exec_data . " $ignore $schema > $data_file" );

        if ( not $opt->nocompress ) {
            $bman->logtext("Compressing $schema sql files");
            $bman->execute( $conf->compress . " ${dest_dir}/*.sql" );
            if ( -e "${schema_file}.gz" ) {
                $schema_file = "${schema_file}.gz";
                $log_data->{'schema_filename'} = $log_data->{'schema_filename'} . '.gz';
            }
            if ( -e "${data_file}.gz" ) {
                $data_file = "${data_file}.gz";
                $log_data->{'data_filename'} = $log_data->{'data_filename'} . '.gz';
            }
        }

        if ( -e $schema_file ) {
            $log_data->{'schema_filesize'} = -s $schema_file;
            $log_data->{'schema_md5sum'}   = file_md5_hex($schema_file);
        }

        if ( -e $data_file ) {
            $log_data->{'data_filesize'} = -s $data_file;
            $log_data->{'data_md5sum'}   = file_md5_hex($data_file);
        }

        if ( $opt->split ) {
            my @files = File::Find::Rule->file->name("*data.sql*")->in($dest_dir);
            foreach my $file ( sort @files ) {
                next if -s $file < 2**30 * 5;
                my $dir  = File::Basename::dirname($file);
                my $base = File::Basename::basename($file);
                $bman->execute("cat $file | split --bytes=1000m -d - $dir/${base}.");
                $bman->execute("rm -f $file");
            }
        }

        if ( not $opt->dryrun ) {
            $log_data->{'backup_ttc_secs'} = time - $start_data_dump;
            my $row = $dbx->resultset('Report::SysDbBackup')->find_or_create( $log_data, { 'key' => 'uidx_schema_filename' } );
            $bman->logtext( "Backup ID: " . $row->sys_db_backup_id . " created" ) if defined $row;
        }
        $pm->finish;
    }
    $pm->wait_all_children;

    $bman->finish;

};
if ($@) {
    $bman->logtext( $@, 'error' );
    $bman->addError($@);
}

if ( defined $bman->errors ) {
    my $email_body = join "\n", @{ $bman->errors };
    my $hostname   = Sys::Hostname::hostname;
    my $amz_mail   = Common::Amazon::Mail->new('html');
    $amz_mail->to( $amz_mail->list->system );
    $amz_mail->subject( 'CRON: DB BACKUP ERRORS: ' . $hostname );
    $amz_mail->body("<pre>$email_body</pre>");
    $amz_mail->sendit;
}
