package CF::Controller::Websockets;

use Mojo::Base 'Mojolicious::Controller';
use Mojo::Pg::PubSub;
use Digest::SHA qw(hmac_sha1_hex);
use POSIX qw(ceil);

use feature 'signatures';
no warnings qw{ experimental::signatures };

use constant SOCKET_INACTIVITY_TIMEOUT => 180;

sub main {
    my $c  = shift;
    my $ip  = $c->tx->remote_address;
    my $key = $c->req->headers->header('Sec-WebSocket-Key');

    $c->inactivity_timeout(SOCKET_INACTIVITY_TIMEOUT);

    if ($c->req->headers->header('Sec-WebSocket-Key') =~ /deflate/) {
        $c->tx->compressed(1);
    }

    my $pubsub = Mojo::Pg::PubSub->new(pg => $c->pg);

    $pubsub->listen(notify => sub($pubsub, $payload) {
        $c->send($payload);
    });

    $c->on(json => sub( $c, $message ) {
        if ( $message->{event} eq 'KEEPALIVE' ) {

            my $user;

            if ($message->{payload} =~ /^\d+$/) {

                $user = $c->pg->db->select('users',
                    [qw(id username secret jitsi_allowed roles banned_until)],
                    { id => $message->{payload}}
                )->hash;
            }

            if ( $user ) {
                my $sig = hmac_sha1_hex($message->{payload}, $user->{secret} );
                if ( $sig ne $message->{sig} ) {
                    $c->app->log->warn(
                        "Invalid signature for " . $user->{username}
                    );
                    $user = undef;
                }
            }

            eval {
                my $tx = $c->pg->db->begin;

                $c->pg->db->delete('sockets', [
                    {id      => $key},
                    {user_id => $message->{payload} || undef },
                ]);

                $c->pg->db->insert('sockets', {
                    id        => $key,
                    ip        => $ip,
                    keepalive => \'now()',
                    user_id   => $user ? $user->{id} : undef,
                });

                $tx->commit;
            };

            my $all = $c->pg->db->query(
                'select count(*) from sockets_view'
            )->hash->{count};

            my $members = $c->pg->db->query(
                'select count(*) from sockets_view where is_member'
            )->hash->{count};

            my $group_size = $c->_member_group_size($members);

            $c->send({json => { event => 'online_users_updated', payload => {
                all             => $all,
                members         => $members,
                group_size_full => $group_size->{full},
                group_size_half => $group_size->{half},
            }}});

            if ( $user ) {
                my $jitsi = $user->{jitsi_allowed} || $user->{roles} =~ /chairman|jitsi/;

                $c->send({json => { event => 'user_status', payload => {
                    jitsi_allowed => $jitsi ? \1:\0,
                    is_banned     => $user->{banned_until} ? \1:\0,
                }}});
            }
        }
    });

    $c->on(finish => sub ($c, $code, $reason = undef) {
        $pubsub->unlisten('notify');
        $c->app->log->debug("WebSocket closed with status $code");
    });
}

sub _member_group_size  ($c, $total = 0){
    my $group = 2 * sqrt($total);
    my $min   = $total / 100;
    my $max   = $total / 5;

    $group = $min if $group < $min;
    $group = $max if $group > $max;

    return {
        full => ceil( $group ),
        half => ceil( $group/2 ),
    };
}

1;