<?php

namespace PCA;

function write_pca_log($string, $level = 'INFO')
{
    echo '[' . $level . '] ' . date('H:i:s') . ' ' . $string . PHP_EOL . '<br />';
    file_put_contents(
        get_log_file(),
        '[' . $level . '] ' . date('H:i:s') . ' ' . $string . PHP_EOL,
        FILE_APPEND | LOCK_EX
    );
}

function get_log_file()
{
    return realpath(__DIR__ . '/../pca_logs') . '/pca.log';
    //return realpath(__DIR__ . '/../pca_logs') . '/' . date('Y-m-d') . '.log';
}

class User
{
    public $user_name;
    public $name;
    public $number_posts;
    public $following;
    public $follows_bot;
    public $last_club_notification = '';

    private static function clean_api_response($api_reponse)
    {
        $data = [];
        if (array_key_exists('data', $api_reponse)) {
            $data = $api_reponse['data'];
        } else {
            $data = $api_reponse;
        }
        return $data;
    }

    public function __construct($api_reponse)
    {
        $data = self::clean_api_response($api_reponse);
        $this->user_name = '@' . $data['username'];
        $this->number_posts = preg_replace('/\s+/', '', $data['counts']['posts']);
        $this->follows_bot = $data['follows_you'];
        $this->name = array_key_exists('name', $data) ? $data['name'] : '';
    }

    public static function get_users_from_api_reponse($api_reponse)
    {
        $data = self::clean_api_response($api_reponse);
        $users = [];
        // Single user
        if (count(array_filter(array_keys($data), 'is_string')) > 0) {
            $users[] = new User($data);
        } else {
            // Multiple users
            foreach ($data as $key => $value) {
                $users[] = new User($value);
            }
        }
        return $users;
    }

    public function get_highest_pca($clubs)
    {
        $key = $this->get_next_pca_key($clubs) - 1;
        if ($key == -1) {
            return [];
        }
        return $clubs[$key];
    }

    public function get_next_pca($clubs)
    {
        return $clubs[$this->get_next_pca_key($clubs)];
    }

    private function get_next_pca_key($clubs)
    {
        if (preg_replace('/\s+/', '', $clubs[0]['post_count']) > $this->number_posts
        ) {
            return 0;
        }
        foreach ($clubs as $key => $club) {
            if (preg_replace('/\s+/', '', $club['post_count']) > $this->number_posts
            ) {
                return $key;
            }
        }
    }
}

class API
{
    public $access_token = '';
    private static $api_endpoint = 'https://api.pnut.io/v1';
    public $max_posttext_length = 256;
    private static $settings_file_location = __DIR__
    . '/..'
    . '/pca_settings/user_settings.json';
    private static $settings = [];
    private static $default_notification_text = 'Congratulations {user.username}, you are now a member of #{pca.name} {pca.emoji} ({pca.postcount} posts)! Next: {nextpca.emoji} at {nextpca.postcount} posts';
    private static $notification_tokens = [
    '{user.username}',
    '{user.name}',
    '{pca.name}',
    '{pca.emoji}',
    '{pca.postcount}',
    '{nextpca.name}',
    '{nextpca.emoji}',
    '{nextpca.postcount}',
    '{posts_to_pca}',
    ];
    public $me;

    public function init()
    {
        write_pca_log('');
        write_pca_log('=================================');
        write_pca_log('');
        $this->max_posttext_length = $this->get_data(
            'https://api.pnut.io/v1/sys/config'
        )['data']['post']['max_length'];
        $this->me = User::get_users_from_api_reponse(
            $this->get_data('https://api.pnut.io/v1/users/me')
        )[0];
        write_pca_log('Hi, my name is ' . $this->me->user_name, 'DEBUG');
        $sapi_type = php_sapi_name();
        $mode = !empty($_SERVER['DOCUMENT_ROOT']) &&
        (substr($sapi_type, 0, 3) == 'cli' || empty($_SERVER['REMOTE_ADDR']))
        ? 'Debug'
        : 'Production';
        write_pca_log('I am currently in  ' . $mode . ' mode');
        if (!file_exists(self::$settings_file_location)) {
            file_put_contents(self::$settings_file_location, json_encode([]));
            write_pca_log(
                'No user settings file found. Creating empty one at ' .
                self::$settings_file_location
            );
        }
        self::$settings = json_decode(
            file_get_contents(self::$settings_file_location),
            true
        );
        write_pca_log('User settings file loaded');
    }

    private function get_data(
        $endpoint,
        $parameters = [],
        $method = 'GET',
        $contenttype = 'application/x-www-form-urlencoded',
        $force = false
    ) {
        write_pca_log('Making request to pnut API at ' . $endpoint);
        $postdata = $this->build_query_string($parameters);
        if ($contenttype == 'application/json') {
            $postdata = json_encode($parameters);
        }
        $context_options = [
        'http' => [
        'method' => $method,
        'header' =>
          'Content-type: ' .
          $contenttype .
          "\r\n" .
          'Authorization: Bearer ' .
          $this->access_token,
        'content' => $postdata,
        ],
        ];
        $sapi_type = php_sapi_name();
        if (!$force
            && !empty($_SERVER['DOCUMENT_ROOT'])
            && (substr($sapi_type, 0, 3) == 'cli' || empty($_SERVER['REMOTE_ADDR']))
            && $method == 'POST'
        ) {
            write_pca_log(
                'Running from shell instead of server. Debug mode assumed. Not submitting POST requests to server!',
                'DEBUG'
            );
            write_pca_log(
                'Would have posted if run on a server: ' . $postdata,
                'DEBUG'
            );
            return;
        }
        $context = stream_context_create($context_options);
        $response = @file_get_contents($endpoint, false, $context);
        if ($response === false) {
            write_pca_log('Received no or invalid response from server', 'ERROR');
            return [
            'meta' => ['code' => '401', 'error_message' => 'Invalid request'],
            ];
        }
        $resp_dict = json_decode($response, true);
        write_pca_log(
            'Got server response. Meta: ' . json_encode($resp_dict['meta'])
        );
        $response_code = $resp_dict['meta']['code'];
        // Success
        if ($response_code >= 200 && $response_code <= 208) {
            return $resp_dict;
        } else {
            write_pca_log(
                'Received error-response from server. ' .
                json_encode($resp_dict['meta']),
                'ERROR'
            );
            // die();
        }
    }

    public function get_messages($clubs)
    {
        $num_unread_endpoint =
        self::$api_endpoint . '/users/me/channels/num_unread/pm';
        $num_unread_pms = $this->get_data($num_unread_endpoint)['data'];
        $messages = []; //Keys: username, values: message-array of messages by the user
        write_pca_log($num_unread_pms . ' unread PMs');
        if ($num_unread_pms > 0) {
            $channels_endpoint = self::$api_endpoint .
            '/users/me/channels/subscribed?include_read=0&channel_types=io.pnut.core.pm';
            $channels = $this->get_data($channels_endpoint);
            foreach ($channels['data'] as $channel) {
                write_pca_log('Channel: ' . $channel['id'], 'DEBUG');
                $messages_endpoint =
                self::$api_endpoint .
                '/channels/' .
                $channel['id'] .
                '/messages?include_deleted=0&include_html=0&include_client=0&include_marker=1';
                $unread_messages = $this->get_data($messages_endpoint);
                $msg = [];
                $sender = '';
                $last_read = $unread_messages['meta']['marker']['last_read_id'];
                foreach ($unread_messages['data'] as $message) {
                    if ($message['id'] <= $last_read) {
                        //Stop when reaching already read messages
                        write_pca_log(
                            'Message ' .
                            $message['id'] .
                            'is <= last read message ' .
                            $last_read .
                            '. Skipping rest of the messages.',
                            'DEBUG'
                        );
                          break;
                    }
                    $sender_tmp = $message['user']['username'];
                    if (substr($this->me->user_name, 1) == $sender_tmp) {
                        //Ignore messages sent by the bot
                        write_pca_log(
                            'Ignoring message ' .
                            $message['id'] .
                            ', because it was sent by myself',
                            'DEBUG'
                        );
                        continue;
                    }
                    $sender = $sender_tmp;
                    $message_text = $message['content']['text'];
                    $msg[] = $message['content']['text'];
                    write_pca_log(
                        'Message from ' . $sender . ': ' . $message_text,
                        'DEBUG'
                    );
                }
                if ($sender != '') {
                    $messages[$sender] = $msg;
                }
            }
        }
        return $messages;
    }

    public function send_message($user, $message)
    {
        $send_message_endpoint =
        self::$api_endpoint . '/channels/pm/messages?update_marker=1';
        $parameters = ['text' => $message, 'destinations' => ['@' . $user]];
        $this->get_data(
            $send_message_endpoint,
            $parameters,
            'POST',
            'application/json',
            $user == 'hutattedonmyarm'
        );
    }

    public function set_notification_text_template($user_id, $text)
    {
        // echo json_encode(self::$settings);
        self::$settings[$user_id] = $text;
        $this->save_settings();
    }

    public function get_notification_text_template($user_id)
    {
        $notification_text = API::$default_notification_text;
        if (array_key_exists($user_id, self::$settings)) {
            write_pca_log(
                $user_id .
                ' has custom settings: ' .
                json_encode(self::$settings[$user_id])
            );
            $notification_text = self::$settings[$user_id];
        } else {
            write_pca_log($user_id . ' does not have custom settings!');
        }
        return $notification_text;
    }

    private function build_notification_text(
        $user,
        $clubs,
        $current_pca,
        $next_pca
    ) {
        $notification_text = $this->get_notification_text_template(
            $user->user_name
        );

        $token_values = [
        $user->user_name,
        $user->name,
        $current_pca['pca'],
        $current_pca['emoji'],
        $current_pca['post_count'],
        $next_pca['pca'],
        $next_pca['emoji'],
        $next_pca['post_count'],
        $next_pca['post_count'] - $user->number_posts,
        ];
        if (array_key_exists($user->user_name, self::$settings)) {
            write_pca_log(
                $user->user_name .
                ' has custom settings: ' .
                json_encode(self::$settings[$user->username])
            );
            $notification_text = self::$settings[$user->username];
        } else {
            write_pca_log($user->user_name . ' does not have custom settings!');
        }
        foreach (self::$notification_tokens as $index => $token) {
            $notification_text = str_replace(
                $token,
                $token_values[$index],
                $notification_text
            );
        }
        return $notification_text;
    }

    public function reset_notification_text_for_user($user_id)
    {
        if (array_key_exists($user_id, self::$settings)) {
            unset(self::$settings[$user_id]);
            $this->save_settings();
        }
    }

    private function save_settings()
    {
        file_put_contents(
            self::$settings_file_location,
            json_encode(self::$settings)
        );
    }
    public function write_post($posttext, $reply_to = -1)
    {
        $post_endpoint = self::$api_endpoint . '/posts';
        $txt = mb_strimwidth($posttext, 0, $this->max_posttext_length, '');
        // $txt = $posttext;
        $parameters = ['text' => $txt];
        if ($reply_to != -1) {
            $parameters['reply_to'] = $reply_to;
        }
        write_pca_log(
            "Writing post: '" . $txt . "' => " . json_encode($parameters)
        );
        $this->get_data($post_endpoint, $parameters, 'POST');
    }

    public function get_user($user_id)
    {
        if ($user_id[0] != '@') {
            $user_id = '@' . $user_id;
        }
        $user_endpoint = self::$api_endpoint . '/users/' . $user_id;
        return new User($this->get_data($user_endpoint));
    }

    public function get_bot_followers()
    {
        $before_id = null;
        $users = [];
        do {
            write_pca_log('Getting followers before id: ' . $before_id);
            $followers_endpoint = self::$api_endpoint . '/users/me/followers';
            if ($before_id != null) {
                $followers_endpoint .= '?before_id=' . $before_id;
            }
            $followers_data = $this->get_data(
                $followers_endpoint, [
                'before_id' => $before_id,
                ]
            );
            $before_id = $followers_data['meta']['min_id'];
            $users = array_merge(
                $users,
                User::get_users_from_api_reponse($followers_data)
            );
        } while ($followers_data['meta']['more'] == '1');
        return $users;
    }

    function build_query_string($array)
    {
        foreach ($array as $k => &$v) {
            if ($v === true) {
                $v = '1';
            } elseif ($v === false) {
                $v = '0';
            }
            unset($v);
        }
        return http_build_query($array);
    }

    public function notify_user($user, $clubs)
    {
        // Get current PCA
        $current_pca_dict = $user->get_highest_pca($clubs);
        $current_pca = '';
        if (array_key_exists('pca', $current_pca_dict)) {
            $current_pca = $current_pca_dict['pca'];
        } else {
            // If key doesn't exist, user hasn't reached a club yet
            return null;
        }
        $last_notification = $user->last_club_notification;
        $next_pca = $user->get_next_pca($clubs);
        $next = [
            'club' => $next_pca,
            'posts_until' => $next_pca['post_count'] - $user->number_posts
        ];
        if ($current_pca != $last_notification) {
            // Haven't notified about the current club yet
            // Stitch together the post itself
            $this->build_notification_text(
                $user,
                $clubs,
                $current_pca_dict,
                $next_pca
            );
            $text_components = [];
            $posttext = 'Congratulations ' .
            $user->user_name .
            ', you are now a member of #' .
            preg_replace('/\s+/', '', $current_pca) .
            ' ' .
            $current_pca_dict['emoji'];
            $text_components[] = ' ('
            . $current_pca_dict['post_count']
            . '+ posts)!';
            $text_components[] = ' Next: ' .
            $next_pca['emoji'] .
            ' at ' .
            $next_pca['post_count'] .
            ' posts';
            foreach ($text_components as $component) {
                $available_length = $this->max_posttext_length - strlen($component);
                if (strlen($posttext) < $available_length) {
                    $posttext .= $component;
                }
            }
            /*
            * Steps to write post as a reply to the post which made a user enter a new club:
            * 1) Get number of user posts (we already have that info)
            * 2) Subtract pca post count value from number posts. This will give us an offset
            * 3) Get their posts with ?count=OFFSET&include_html=0&include_counts=0&include_client=0
            * 4) Check if counts match. If not, get more of their posts with before_id set to the relevant ID
            * 5) Repeat 4 until we have the correct post
            * 6) Reply to it
            */
            $pca_offset = $user->number_posts - $current_pca_dict['post_count'] + 1;
            $before_id = -1;
            $loop_counts = 10;
            do {
                $ep = self::$api_endpoint .
                '/users/' .
                $user->user_name .
                '/posts?count=' .
                $pca_offset .
                '&include_html=0&include_counts=0&include_client=0&include_deleted=1';
                // write_pca_log($ep, "DEBUG");
                $response = $this->get_data($ep, []);
                $pca_offset -= count($response['data']);
                $before_id = $response['meta']['min_id'];
                write_pca_log(
                    'Received ' . count($response['data']) . ' posts',
                    'DEBUG'
                );
                $loop_counts--;
                // write_pca_log("Remaining offset: ".$pca_offset);
            } while ($pca_offset > 0 && $loop_counts > 0);
            $reply_to = $response['data'][count($response['data']) - 1]['id'];
            // reached mac loop iterations
            if (!isset($reply_to) || $reply_to == 0 || $loop_counts <= 0) {
                $reply_to = -1;
            }
            $del_ar = $response['data'][count($response['data']) - 1];
            $deleted = array_key_exists('is_deleted', $del_ar) &&
            $del_ar['is_deleted'] == 'true';
            $log_string = 'Post that made them reach ' .
            preg_replace('/\s+/', '', $current_pca) .
            ': ' .
            $reply_to .
            '. Deleted?: ';
            $log_string .= $deleted ? 'yes' : 'no';
            write_pca_log($log_string);
            $this->write_post($posttext, $reply_to);
            $user->last_club_notification = $current_pca;
            write_pca_log($posttext);
            write_pca_log($log_string);
            $now = \DateTime::createFromFormat('U.u', microtime(true));
            $recent_changes_dict = [
            'date' => $response['data'][count($response['data']) - 1]['created_at'],
            'user' => $user->user_name,
            'pca' => $current_pca,
            'post_id' => $reply_to,
            ];
            $history_file = 'history.json';
            $inp = file_get_contents($history_file);
            $tempArray = json_decode($inp, true);
            if ($tempArray == null) {
                $tempArray = [];
            }
            array_push($tempArray, $recent_changes_dict);
            file_put_contents($history_file, json_encode($tempArray));
        } else {
            // Already notified, nothing to do
            write_pca_log(
                $user->user_name .
                ' has already been notified for reaching ' .
                $current_pca
            );
        }
        return $next;
    }
}
// Get all clubs
write_pca_log('');
write_pca_log('');
write_pca_log('');
write_pca_log('');
write_pca_log('Log file: ' . get_log_file());
$clubs = json_decode(file_get_contents('https://pca.phlaym.net/pca.php'), true);
write_pca_log('Found clubs: ' . implode(', ', array_column($clubs, 'pca')));
// $last_notification_file = '../last_notification.json';
$last_notification_file_dir = realpath(__DIR__ . '/../pca_settings');
$last_notification_file_name = '/last_notification.json';
$last_notification_file = $last_notification_file_dir
. $last_notification_file_name;
write_pca_log('Last notification file: ' . $last_notification_file);
$last_notification_dict = [];

if (file_exists($last_notification_file)) {
    $last_notification_dict = json_decode(
        file_get_contents($last_notification_file),
        true
    );
    write_pca_log('Loaded last notification info from file');
} else {
    write_pca_log('Last notification info file could not be found');
}

// Get users
$access_token = '-1';
$token_file = realpath(__DIR__ . '/../pca_settings/access_token');
if ($token_file !== false) {
    $access_token = file_get_contents($token_file);
    write_pca_log('Using saved authentication');
    if ($access_token == false) {
        write_pca_log('Re-authenticating');
        header('Location: http://pca.phlaym.net/pnutauth.php');
    }
} else {
    write_pca_log('Re-authenticating');
    header('Location: http://pca.phlaym.net/pnutauth.php');
}
$api = new API();
$api->access_token = $access_token;
$api->init();
$messages = $api->get_messages($clubs);
/*
 * Message command syntax:
 * "Help" or "?" => prints help
 * "Get notification text" => replies with notification text
 * "Reset notification text" => resets to default
 * "Set notification text" followed by a space and then the text. Replies with sample text. Available tokens see class var
 */
foreach ($messages as $sender => $message) {
    $sender_obj = $api->get_user($sender);
    foreach ($message as $msg) {
        if (mb_strtolower($msg) == '?' || mb_strtolower($msg) == 'help') {
            $helptext = 'Welcome to ' . $api->me->user_name . "!\n";
            $helptext .= "The following commands are available: \n";
            $helptext .=
            "- 'Get notification text', which prints the current notification text for you\n";
            $helptext .=
            "- 'Set notification text', followed by a space and a custom notification text\n";
            $helptext .=
            "- 'Reset notification text', resets the notification text to the default\n";
            $helptext .= "\n";
            $helptext .= "		Available tokens for the custom notification text\n";
            $helptext .= "		- '{user.username}', your username (including the @)\n";
            $helptext .= "		- '{user.name}', your real name\n";
            $helptext .=
            "		- '{pca.name}', the name of the PCA you achieved, excluding the #\n";
            $helptext .= "		- '{pca.emoji}', the emoji of the PCA you achieved\n";
            $helptext .=
            "		- '{pca.postcount}', the number of posts you need to achieve the PCA\n";
            $helptext .= "		- '{nextpca.name}', the name of the next PCA\n";
            $helptext .= "		- '{nextpca.emoji}', the emoji of the next PCA\n";
            $helptext .=
            " 	- '{nextpca.postcount}', the total number of posts needed for the next PCA\n";
            $helptext .=
            "		- '{posts_to_pca}', the number of posts missing to the next PCA\n";
            $api->send_message($sender, $helptext);
            //Print help text
        } elseif (substr(mb_strtolower($msg), 0, 21) === 'get notification text') {
            //Print current notification text for user
            //TODO: build example notification text. Create user object and do 'get_highest_pca($clubs)' and 'get_next_pca($clubs)'
            $api->send_message(
                $sender,
                "You current notification text is: \n" .
                $api->get_notification_text_template($sender)
            );
        } elseif (substr(mb_strtolower($msg), 0, 23) === 'reset notification text'
        ) {
            //reset notification text for user to default
            $api->reset_notification_text_for_user($sender);
            $api->send_message(
                $sender,
                "Your notification text has been reset back to the default: \n" .
                $api->get_notification_text_template($sender)
            );
        } elseif (substr(mb_strtolower($msg), 0, 21) === 'set notification text') {
            $notification_text = substr($msg, 22);
            $api->set_notification_text_template($sender, $notification_text);
            $api->send_message(
                $sender,
                "Your notification text has been set to:\n" .
                $api->get_notification_text_template($sender)
            );
        } else {
            write_pca_log(
                "Unknown command: '"
                . $msg
                . "' from "
                . $sender,
                'DEBUG'
            );
            $api->send_message(
                $sender,
                "I'm sorry, I don't recognize that. Try PMing me 'help'"
            );
        }
    }
}
$followers = $api->get_bot_followers();
// Check and notify. Also build notification dict
$next_club_infos = [];
foreach ($followers as $user) {
    if ($last_notification_dict != null
        && array_key_exists($user->user_name, $last_notification_dict)
    ) {
        write_pca_log(
            $user->user_name .
            ' has been notified in the past. Last time when reaching: ' .
            $last_notification_dict[$user->user_name]
        );
        $user->last_club_notification = $last_notification_dict[$user->user_name];
    } else {
        write_pca_log($user->user_name . ' has never been notified in the past');
    }
    $next_club_info = $api->notify_user($user, $clubs);
    if ($next_club_info !== null) {
        $next_club_info['user'] = $user->user_name;
        $next_club_infos[] = $next_club_info;
    }
    if ($user->last_club_notification != '') {
        write_pca_log(
            'Saving last notification ' .
            $user->last_club_notification .
            ' for user ' .
            $user->user_name
        );
        $last_notification_dict[$user->user_name] = $user->last_club_notification;
    } else {
        write_pca_log(
            $user->user_name .
            ' will not be saved, as they have not reached a club yet'
        );
    }
}
usort(
    $next_club_infos,
    fn($a, $b) => intval($a['posts_until']) - intval($b['posts_until'])
);
foreach ($next_club_infos as $idx => $value) {
    if ($idx >= 10) {
        break;
    }
    write_pca_log(
        'Next club will be: '
        . $value['club']['pca']
        . ' for user '
        . $value['user']
        . ' in '
        . $value['posts_until']
        . ' posts'
    );
}

// Save changes
write_pca_log('Saving updated notification info');
$f = fopen($last_notification_file, 'w');
fwrite($f, json_encode($last_notification_dict));
fclose($f);

?>