#! /usr/bin/perl
use strict;
use warnings;
no warnings qw(once);
use JSON::XS;
use LWP::UserAgent;
use DBI;
use POSIX;
use AnyEvent::WebSocket::Client;
use AnyEvent::Loop;
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';
use utf8;

require '../include/config.pm';

my $ua = LWP::UserAgent->new('SKVidarLang/1.0');
our $ws_disconnected;

sub db_connect {
	my $dbh = DBI->connect("dbi:Pg:dbname=$config::dbname;host=127.0.0.1", $config::dbuser, $config::dbpass, {RaiseError => 1})
		or warn "Could not connect to Postgres: " . DBI->errstr;
	return $dbh;
}

my $dbh;
while (1) {
	if (!defined($dbh) || !$dbh->ping) {
		$dbh = db_connect();
		if (!defined($dbh)) {
			sleep 1;
			next;
		}
	}
	$ws_disconnected = AnyEvent->condvar;
	my $response = $ua->post('https://slack.com/api/apps.connections.open',
		Authorization => 'Bearer ' . $config::slack_app_token,
		Content_type => 'application/x-www-form-urlencoded'
	);
	if (!$response->is_success) {
		warn "apps.connections.open: " . $response->status_line;
		sleep 1;
		next;	
	}
	my $msg_json = JSON::XS::decode_json($response->decoded_content);
	if (!defined($msg_json) || !$msg_json->{'ok'}) {
		warn "Something went wrong: " . $response->decoded_content;
		sleep 1;
		next;
	}

	my $ws_url = $msg_json->{'url'};
	my $ws = AnyEvent::WebSocket::Client->new;
	$ws->connect($ws_url)->cb(\&ws_cb);
	$ws_disconnected->recv;
	print STDERR "Disconnected; trying to reconnect.\n\n";
};

sub ws_cb {
	our $connection = eval { shift->recv };
	if ($@) {
		warn $@;
		sleep 1;
		$ws_disconnected->send;
		return;
	}

	print STDERR "Connected to the Slack WebSocket.\n";

	$connection->on(each_message => sub {
		my ($conn, $message) = @_;
		my $now = [Time::HiRes::gettimeofday];
		print STDERR "Message: $message->{'body'}\n";
		my $json = JSON::XS::decode_json($message->{'body'});
		eval {
			if (exists($json->{'payload'}{'event'})) {
				if (exists($json->{'payload'}{'event'}{'event_ts'}) &&
				    $json->{'payload'}{'event'}{'event_ts'} =~ /(\d+)\.(\d+)/) {
					my $elapsed = Time::HiRes::tv_interval([$1, $2], $now);
					printf STDERR "Message used %.1f ms to reach us.\n", 1e3 * $elapsed;
				}
				handle_event($json->{'payload'}{'event'});
			}
		};
		if ($@) {
			print STDERR "Error during handling: $@";
			die;
		} elsif (exists($json->{'envelope_id'})) {
			my $ack = { envelope_id => $json->{'envelope_id'} };
			print STDERR "Ack: " . JSON::XS::encode_json($ack) . "\n";
			$conn->send(JSON::XS::encode_json($ack))
				or die "Error sending ack: $!";
		}
	});

	$connection->on(finish => sub {
		my ($conn, $msg) = @_;
		$msg //= '(none)';
		print STDERR "Finished with message: $msg\n";
		$ws_disconnected->send;
	});
}

sub mark {
	print STDERR "Marking that a sync is needed.\n";
	$dbh->do('NOTIFY skvupdate');
}

sub handle_event {
	my $ev = shift;
	if (!exists($ev->{'type'})) {
		print STDERR "Has no type; ignoring.\n";
		return;
	}

	my $type = $ev->{'type'};
	my $user = $ev->{'user'};

	if ($type eq 'message') {
		if ($ev->{'message'}{'text'} =~ /(20\d{2}-\d{2}-\d{2})/) {
			# TODO: What if edits happen out-of-order?
			my $date = $1;
			my $channel = $ev->{'channel'};
			my $ts = $ev->{'message'}{'ts'};
			print STDERR "Matching message {$channel, $ts} to date $date\n";
			$dbh->do('INSERT INTO message_sheet_link (channel, ts, sheet_title) VALUES (?,?,?)', undef,
				$channel, $ts, $date);
			# Blow the cache.
			$dbh->do('UPDATE message_sheet_link SET tab_name=NULL, tab_id=NULL WHERE channel=?', undef, $channel);
		} else {
			print STDERR "No date found in message, ignoring\n";
		}
		return;
	}

	my $reaction = $ev->{'reaction'};
	my $channel = $ev->{'item'}{'channel'};
	my $ts = $ev->{'item'}{'ts'};
	my $event_ts = $ev->{'event_ts'};

	if (!defined($channel) || !defined($ts)) {
		print STDERR "Not reacting to a message; ignoring.\n";
		return;
	}

	if ($type eq 'reaction_added' || $type eq 'reaction_removed') {
		$dbh->do('INSERT INTO reaction_log (userid, channel, ts, reaction, event_type, event_ts) VALUES (?,?,?,?,?,?)', undef,
			$user, $channel, $ts, $reaction, $type, $event_ts);
		mark($dbh);
	} else {
		print STDERR "Type is $type (not a reaction added or removed); ignoring.\n";
	}
}
