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-Extensions') =~ /permessage-deflate/) {
        $c->tx->compressed(1);
        $c->res->headers->add('Sec-WebSocket-Extensions' => 'permessage-deflate');
    }

    $c->redis->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 ? {user_id => $user->{id}}:{}
                ]);

                $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) {
        $c->redis->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;