#!/usr/bin/env perl

# https://renotes.ru/post-392/perl-telegram-bot

use feature 'say';
use strict;
use warnings;

use Time::HiRes qw(time);
use DateTime;
use WWW::Telegram::BotAPI;
use Data::Dumper;
use Encode;
use File::Slurp;
use JSON::PP;
use File::Basename qw(dirname);

use constant {
    TOKEN => decode_json(sprintf("%s", join('', read_file(sprintf("%s/conf.json", dirname(__FILE__))))))->{token},
    WINKING_FACE => '😉',
    ANONYMOUS    => 'Anonymous',
    TIMEOUT      => 30
};

$| = 1;

main();

sub main {
    my ($offset, $updates) = 0;

    my $api = WWW::Telegram::BotAPI->new(token => TOKEN);

    my $chatbot = eval { $api->getMe; } or do {
        my $e = $api->parse_error->{msg};

        die sprintf("Can not get chatbot: %s", $e);
    };

    while (1) {
        my $updates = eval { $api->getUpdates({timeout => TIMEOUT, $offset > 0 ? (offset => $offset) : ()}); } or do {
            my $e = $@;

            bot_log(sprintf("[FAILURE] bot can not get updates: %s", $e));

            sleep TIMEOUT;

            next;
        };

        next unless $updates && %{$updates} && $updates->{ok};

        foreach my $update (@{$updates->{result}}) {
            my $text;
            my $chat;

            $offset = $update->{update_id} + 1 if $update->{update_id} >= $offset;

            bot_log(sprintf("new update %d for bot with keys: %s", $offset, join(',', keys(%{$update}))));

            if ($update->{chat_join_request}) {

                # bot_log(Dumper($update->{chat_join_request}));

                if ($update->{chat_join_request}->{chat} && $update->{chat_join_request}->{from}) {
                    my $channel         = $update->{chat_join_request}->{chat};
                    my $private_chat_id = $update->{chat_join_request}->{user_chat_id};

                    my $result = eval {

                        # Accept user on primary channel
                        $api->approveChatJoinRequest(
                            {
                                chat_id => $channel->{id},
                                user_id => $update->{chat_join_request}->{from}->{id},
                            }
                        );

                        # DM hello message to user
                        $api->sendMessage(
                            {
                                chat_id => $private_chat_id,
                                text    => decode(
                                    'utf-8',
                                    sprintf(
"Hi, %s! You're accepted on Ethelia official Telegram channel. Now you can get your test account at Ethelia service. Type /start to continue, it will take a few minutes but I promise — it will be curious journey. Here we go!",
                                        $update->{chat_join_request}->{from}->{first_name})
                                ),
                            }
                        );

                        bot_log(
                            sprintf(
                                "User %s is accepted on %s channel",
                                $update->{chat_join_request}->{from}->{first_name},
                                encode('utf-8', $channel->{title})
                            )
                        );

                        1;
                    } or do {
                        my $e = $@;

                        bot_log(
                            sprintf(
                                "[FAILURE] can not accept user %s to join channel %s: %s",
                                $update->{chat_join_request}->{from}->{first_name},
                                encode('utf-8', $channel->{title}), $e
                            )
                        );
                    };
                }

                next;
            }

            if ($update->{callback_query} && $update->{callback_query}{data} && $update->{callback_query}{message}) {
                $text = $update->{callback_query}{data};
                $chat = $update->{callback_query}{from};
            }
            else {
                $text = $update->{message}{text};
                $chat = $update->{message}{chat};
            }

            next unless $text && $chat && %{$chat};

            bot_log(sprintf("request from %s: %s", ($chat->{username} // $chat->{first_name} // ANONYMOUS), $text));

            if ($text =~ m/^\/([a-z]+)/i) {
                my $comm = $1;

                my $res = eval {
                    my $status = 0;

                    if ($comm =~ /start|ping|pong/) {
                        $status = command_process($api, $chat, $comm);
                    }
                    else {
                        $status = command_decline($api, $chat, $comm);
                    }

                    bot_log(
                        sprintf(
                            "successfully processed command %s from %s with status %s",
                            $comm, ($chat->{username} // $chat->{first_name} // ANONYMOUS), $status
                        )
                    );

                    1;
                } or do {
                    my $e = $@;

                    bot_log(
                        sprintf(
                            "[FAILURE] can not process command %s for user %s: %s",
                            $comm, ($chat->{username} // $chat->{first_name} // ANONYMOUS), $e
                        )
                    );
                };
            }
        }
    }

    return 1;
}

sub command_process {
    my ($apiobj, $chat, $command) = @_;

    if ($command ne 'start') {
        $apiobj->sendMessage(
            {
                chat_id => $chat->{id},
                text    => sprintf("command %s is accepted", $command),
            }
        );
    }
    else {
        $apiobj->sendMessage(
            {
                chat_id      => $chat->{id},
                text         => decode('utf-8', sprintf("Hi, stranger! Let's talk a bit %s", WINKING_FACE)),
                reply_markup => {
                    inline_keyboard => [
                        [
                            {
                                text          => 'ping',
                                callback_data => '/ping'
                            },
                            {
                                text          => 'pong',
                                callback_data => '/pong'
                            }
                        ]
                    ]
                }
            }
        );
    }

    return 1;
}

sub command_decline {
    my ($apiobj, $chat, $command) = @_;

    $apiobj->sendMessage(
        {
            chat_id => $chat->{id},
            text    => sprintf("command %s is not supported", $command),
        }
    );

    return 0;
}

sub bot_log {
    my ($message) = @_;

    say STDOUT sprintf("%s: %s", DateTime->from_epoch(epoch => time())->strftime("%F %T.%6N"), $message);
}

1;
