#!/usr/bin/perl
use strict;
use warnings;

use Data::Dumper;
use Time::Local;
use SKM::Common;
use Node::Conf;
use SKM::DB;
use File::Basename qw(dirname);
use lib dirname(__FILE__).'/../lib';              # find  SM::Config here
use SM::Config ":all";

# CONS
my $APL = $ENV{APL};
my $APL_VAR = "$ENV{APL_VAR}";
my $IDENTITY_OBJ = 53;
my $CLOUD_PREFIX = "store";
my $BUCKET = UNI;
my $LIMIT = 500;		# Max elements to process per turn
my $TASK_TIMEOUT = 30;		# 30 sec to wait until cloud task complete

# VAR
my $dbm;
my $sigterm;
my $TotalWiped = 0;
my $LastEventid = 0;
my %dbs;
my %Tasks;
my %DevTypes;
my %CloudConf;

# SIG
$SIG{TERM} = sub { $sigterm = 1 }; # SIGTERM terminates mover

# SUB
sub db_master
{
	eval { 
		$dbm->disconnect() if $dbm; 
		$dbm=''; 
	}; # disconnect if defined
	
	for (my $i=1; $i<6; $i++) {
		eval {
			$dbm = DBMaster({PrintError=>1,RaiseError => 1});
			
			$dbs{DELETE_CHUNK} = $dbm->prepare("DELETE FROM cloud_chunks WHERE eventid=? AND objid=? AND chunkid=?");
			$dbs{DELETE_TASK} = $dbm->prepare("DELETE FROM cloud_wipe_tasks WHERE eventid=?");
		};
		if ($@) {
			SM_LOG->logdie("Attempt $i (final). Cannot connect to master: $@") if $i>=5;
    			SM_LOG->error("Attempt $i. Cannot connect to master: $@");
    			SM_LOG->error('Sleep '. $i*30 . ' before next attempt');
    			SM_error('',  "Cannot connect to master:$@");
    			sleep($i*30);
		}
		else {
			last;
		}
	}
	
	$dbm->{FetchHashKeyName} = 'NAME_uc';
	$dbm->{ShowErrorStatement}=1;
	$dbm->{AutoCommit} = 1;
	SM_LOG->debug("Connected to master db");
}

sub fetch_device_types
{
	eval {
		my $ra = $dbm->selectall_arrayref(qq{
			select obj,subtype from _objs 
			where deleted=0 and otype='D' and subtype != 'N'
		});
		foreach my $r (@$ra) {
			$DevTypes{$r->[0]} = $r->[1];
		}
	};
	if ($@) {
		SM_LOG->logdie("DB_MASTER: $@");
	}

}

sub dev
{
	my $objid = shift;

	my $type = $DevTypes{$objid};
	return $objid if not $type or $type eq 'C';
	return lc($type).$objid;
}

sub check_cloud
{
	eval {
		my $c = $dbm->selectall_arrayref( qq {
			select attr,val from _obj_attr 
			where obj=$IDENTITY_OBJ and attr in
			('CLOUD_STORAGE','CLOUD_STORAGE_ENABLED',
			'CLOUD_KEY_ID','CLOUD_SECRET_KEY','CLOUD_BUCKET')
		});
		foreach my $av (@$c) {
			$CloudConf{$av->[0]} = $av->[1];
		}
	};
	if ($@) {
		SM_LOG->logdie("DB_MASTER: $@");
	}
	return $CloudConf{CLOUD_STORAGE} ne 'none' ? 1 : 0;
}


sub cloud_init
{
	eval {
		no strict 'subs';
		require SM::Cloud;
		SM::Cloud->import($CloudConf{CLOUD_STORAGE});
		Cloud_Init(
			$CloudConf{CLOUD_KEY_ID},
		        $CloudConf{CLOUD_SECRET_KEY}
		);
	
		# If CLOUD_BUCKET system setting is specified 
		# it overrides bucket value which defaults to node UNI
		#
		$BUCKET = $CloudConf{CLOUD_BUCKET} if $CloudConf{CLOUD_BUCKET};
		
		my @buckets = Cloud_ListBuckets();
		my $found = 0;
		foreach my $bucket (@buckets) {
			if ($bucket eq $BUCKET) {
				$found = 1;
				last;
			}
		}
		die "Bucket $BUCKET is missing\n" if not $found;
	};
	if ($@) {
		SM_LOG->logdie("Cloud init failed: $@");
	}
}

sub scan_tasks
{
	my $cnt = 0;
	eval {
		$cnt = $dbm->do(qq{
			insert into tmp_wipe_tasks (
			 select t.eventid, c.objid, c.chunkid, t.created_at
			 from cloud_wipe_tasks t left outer join cloud_chunks c
			 on t.eventid=c.eventid
			 where t.eventid > $LastEventid
			 order by t.created_at
			 limit $LIMIT 
			)
		});
		my $ra = $dbm->selectrow_arrayref("select max(eventid) from tmp_wipe_tasks");
		$LastEventid = $ra->[0] || 0;
	};
	die "DB_MASTER: $@" if $@;
	return $cnt;
}

sub wipe_empty_tasks
{
	my $wiped = 0;
	eval {
		$wiped = $dbm->do(qq {
			delete from cloud_wipe_tasks where eventid in
			(select distinct eventid from tmp_wipe_tasks
			  where objid is null)
		});
	};
	die "DB_MASTER: $@" if $@;
	
	SM_LOG->info("Empty tasks wiped: $wiped");
	print  "Empty tasks wiped: $wiped\n";
}

sub delete_chunk 
{
	my ($eventid, $objid, $chunkid) = @_;
	my ($streamnum, $chunk) = split(/-/, $chunkid);
	
	my $dev = dev($objid);
	my $path = $CLOUD_PREFIX . "/$dev/$streamnum/$chunk";
	
	eval {
		# Remove chunk
		Cloud_DeleteFile($BUCKET, $path);
		# Remove index
		Cloud_DeleteFile($BUCKET, $path.".idx");
		# Remove chunk record from 'cloud_chunks'
		$dbs{DELETE_CHUNK}->execute($eventid, $objid, $chunkid);
	};
	if ($@) {
	        $sigterm = 1 if $@ =~ /SIGTERM/;
		SM_LOG->warn("chunk $path delete failed: $@");
		return 0;
	}
	return 1;
}

sub wipe_chunks
{
	my $r;
	my $wiped = 0;
	eval {
		$r = $dbm->selectall_arrayref(
			"SELECT eventid,objid,chunkid from tmp_wipe_tasks"
		);
	};
	die "DB_MASTER: Error fetching chunks: $@" if $@;
	
	my %tasks;
	my %err;
	foreach my $row (@$r) {
		my ($eventid,$objid,$chunkid) = @$row;
		next if not $objid or not $chunkid;
		if (not $tasks{$eventid}) {
			$tasks{$eventid} = [ [$objid, $chunkid ] ];
		} else {
			push @{$tasks{$eventid}}, [$objid, $chunkid ];
		}
		$err{$eventid} = 0;
	}
	
	foreach my $eventid (keys %tasks) {
		my @chunks = @{$tasks{$eventid}};
		
		foreach my $chunk (@chunks) {
			my $ok = delete_chunk($eventid, @$chunk);
			$err{$eventid}++ if not $ok;
			$wiped++ if $ok;
			print "@$chunk: ".($ok?"OK":"ERROR")."\n";
			die "SIGTERM" if $sigterm;
		}
		
		# Delete task if all chunks wiped successfully
		#
		if ($err{$eventid} == 0) {
			eval {
				$dbs{DELETE_TASK}->execute($eventid);
			};
			die "DB_MASTER: $@" if $@;
		}
	}
	
	$TotalWiped += $wiped;
	print "Wiped: $wiped; TotalWiped: $TotalWiped\n";
	return $wiped;
}

sub prepare
{
	# Create temporary table for intermediate results
	#
	eval {
		$dbm->do(qq{
			create temporary table tmp_wipe_tasks
			(eventid integer, objid integer, chunkid varchar, created_at timestamp without time zone)
		});
	};
	die "DB_MASTER: $@" if $@;
}

sub cleanup
{
	eval { $dbm->do("delete from tmp_wipe_tasks") };
	die "DB_MASTER: $@" if $@;
}

sub wipe
{
	my $iter = shift;
	
	# Scan 'cloud_wipe_tasks' table
	#
	my $cnt = scan_tasks;
	
	# Delete entries from 'cloud_wipe_tasks' that
	# do not match any chunk in cloud
	#
	wipe_empty_tasks;
	
	# Delete chunks from cloud
	#
	my $wiped = wipe_chunks;
	SM_LOG->info("(iter $iter) Wiped: $wiped chunks; Total: $TotalWiped");
	
	# Cleanup temporary table
	#
	cleanup;
	
	return $cnt;
}

sub wiper
{
	eval {
		prepare;
		my $cnt = 0;
		my $iter = 1;
		do {
		        die "SIGTERM" if $sigterm;
			$cnt = wipe($iter);
			$iter++;
		} until $cnt < $LIMIT;
	};
	if ($@) {
		if ($@ =~ /^SIGTERM/) {
			SM_LOG->warn("TERMINATED by request. The task is not completed");
			exit 1;
		} elsif ($@ =~ /^DB_MASTER/) {            # problems with master DB
			SM_LOG->logdie("TERMINATED. $@");
		} else {
			SM_LOG->logdie("TERMINATED. Unknown Error: $@");
		}
	}
}

sub main 
{
	SM_LOG->info("Cloud WIPE started");

        # Pid control
        #
        die "Concurrent run detected!" if CheckPid;
        WritePid;

        # Connect to Master DB
        # 
        db_master;

        die "No cloud configured!\n" unless check_cloud;

        # Connect to Cloud storage and validate its state
        #
        cloud_init;
	
	# Fetch device types
	#
	fetch_device_types;

        # Read task list from DB  and wipe corresponding chunks
        #
        wiper;
}

# MAIN
main;

END {
	RemovePid;
	eval { $dbm->disconnect } if $dbm;
	SM_LOG->info("Cloud WIPE finished");
}
