<?php

/**
 *
 * AI Labs extension
 *
 * @copyright (c) 2023, privet.fun, https://privet.fun
 * @license GNU General Public License, version 2 (GPL-2.0)
 *
 */

namespace privet\ailabs\includes;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
use privet\ailabs\includes\resultParse;

class AIController
{
    protected $auth;
    protected $config;
    protected $db;
    protected $helper;
    protected $language;
    protected $request;
    protected $template;
    protected $user;
    protected $phpbb_container;
    protected $php_ext;
    protected $root_path;
    protected $users_table;
    protected $jobs_table;

    protected $job_id;
    protected $start;
    protected $log;
    protected $job;
    protected $cfg;
    protected $lang;

    public function __construct(
        \phpbb\auth\auth $auth,
        \phpbb\config\config $config,
        \phpbb\db\driver\driver_interface $db,
        \phpbb\controller\helper $helper,
        \phpbb\language\language $language,
        \phpbb\request\request $request,
        \phpbb\template\template $template,
        \phpbb\user $user,
        ContainerInterface $phpbb_container,
        $php_ext,
        $root_path,
        $users_table,
        $jobs_table
    ) {
        $this->auth = $auth;
        $this->config = $config;
        $this->db = $db;
        $this->helper = $helper;
        $this->language = $language;
        $this->request = $request;
        $this->template = $template;
        $this->user = $user;
        $this->phpbb_container = $phpbb_container;
        $this->php_ext = $php_ext;
        $this->root_path = $root_path;
        $this->users_table = $users_table;
        $this->jobs_table = $jobs_table;
    }

    /**
     * @return \Symfony\Component\HttpFoundation\Response A Symfony Response object
     */
    public function execute()
    {
        $this->start = date('Y-m-d H:i:s');

        // https://symfony.com/doc/current/components/http_foundation.html#streaming-a-response
        $streamedResponse = new StreamedResponse();
        $streamedResponse->headers->set('X-Accel-Buffering', 'no');
        $streamedResponse->setCallback(function () {
            var_dump('Processing');
            flush();
        });
        $streamedResponse->send();

        $this->job_id = utf8_clean_string($this->request->variable('job_id', '', true));

        if (empty($this->job_id)) {
            return new JsonResponse('job_id not provided');
        }

        $where = [
            'job_id' => $this->job_id
        ];

        $sql = 'SELECT j.job_id, j.ailabs_user_id, j.status, j.attempts, j.post_mode, j.post_id, j.forum_id, j.poster_id, j.poster_name, j.request, c.config, c.template, u.username as ailabs_username, p.topic_id, p.post_subject, p.post_text, f.forum_name ' .
            'FROM ' . $this->jobs_table . ' j ' .
            'JOIN ' . $this->users_table . ' c ON c.user_id = j.ailabs_user_id ' .
            'JOIN ' . USERS_TABLE . ' u ON u.user_id = j.ailabs_user_id ' .
            'JOIN ' . POSTS_TABLE . ' p ON p.post_id = j.post_id ' .
            'JOIN ' . FORUMS_TABLE . ' f ON f.forum_id = j.forum_id ' .
            'WHERE ' . $this->db->sql_build_array('SELECT', $where);
        $result = $this->db->sql_query($sql);
        $this->job = $this->db->sql_fetchrow($result);
        $this->db->sql_freeresult($result);

        if (empty($this->job)) {
            return new JsonResponse('job_id not found in the database');
        }

        if (!empty($this->job['status'])) {
            return new JsonResponse('job_id already completed with ' . $this->job['status']);
        }

        $this->log = array('start' => $this->start);

        $this->job['request'] = utf8_decode_ncr($this->job['request']);

        try {
            $this->cfg = json_decode($this->job['config']);
        } catch (\Exception $e) {
            $this->cfg = null;
            $this->log['exception'] = $e->getMessage();
        }

        if (empty($this->cfg)) {
            $this->job['status'] = 'fail';
            $this->log['error'] = 'config not provided';

            $set = [
                'status'            => $this->job['status'],
                'log'               => json_encode($this->log)
            ];

            $this->job_update($set);
            $this->post_update($this->job);

            return new JsonResponse($this->log);
        }

        return $this->process();
    }

    protected function process()
    {
        return new JsonResponse($this->log);
    }

    /**
     * Parse message template
     * @param string $template
     */
    protected function replace_vars($job, resultParse $resultParse)
    {
        $images = null;
        $attachments = null;
        if (!empty($resultParse->images)) {
            $images = [];
            $attachments = [];
            $ind = 0;
            foreach ($resultParse->images as $item) {
                array_push($images, '[img]' . $item . '[/img]' . PHP_EOL . PHP_EOL);
                array_push($attachments, '[attachment=' . $ind . '][/attachment]' . PHP_EOL);
                $ind = $ind + 1;
            }
            $images = implode("", $images);
            $attachments = implode("", $attachments);
        }

        $tokens = array(
            '{post_id}'         => $job['post_id'],
            '{request}'         => $job['request'],
            '{info}'            => $resultParse->info,
            '{response}'        => $resultParse->message,
            '{images}'          => $images,
            '{attachments}'     => $attachments,
            '{poster_id}'       => $job['poster_id'],
            '{poster_name}'     => $job['poster_name'],
            '{ailabs_username}' => $job['ailabs_username'],
        );

        return str_ireplace(array_keys($tokens), array_values($tokens), $job['template']);
    }

    protected function job_update($set)
    {
        $where = ['job_id' => $this->job_id];

        $sql = 'UPDATE ' . $this->jobs_table .
            ' SET ' . $this->db->sql_build_array('UPDATE', $set) .
            ' WHERE ' . $this->db->sql_build_array('SELECT', $where);

        $result = $this->db->sql_query($sql);

        $this->db->sql_freeresult($result);
    }

    protected function log_flush()
    {
        $set = ['log' => json_encode($this->log)];

        $this->job_update($set);
    }

    protected function post_update($job)
    {
        /* 
            [
                job_id: <int>,                         
                ailabs_user_id: <int>,
                ailabs_username: <string>, 
                response_time: <int>,
                status: <string>, 
                response_post_id: <int>
            ]
        */
        $data = array(
            'job_id' => $job['job_id'],
            'ailabs_user_id' => $job['ailabs_user_id'],
            'ailabs_username' => $job['ailabs_username'],
            'status' => $job['status'],
            'response_time' => empty($job['response_time']) ? time() : $job['response_time'],
        );
        if (!empty($job['response_post_id'])) {
            $data['response_post_id'] = $job['response_post_id'];
        }
        $where = [
            'post_id' => $job['post_id']
        ];
        $set =  '\'' . json_encode($data) . ',\'';
        $concat = $this->db->sql_concatenate('post_ailabs_data', $set);
        $sql = 'UPDATE ' . POSTS_TABLE . ' SET post_ailabs_data = ' . $concat . ' WHERE ' . $this->db->sql_build_array('SELECT', $where);
        $result = $this->db->sql_query($sql);
        $this->db->sql_freeresult($result);
    }

    protected function post_response($job, $response)
    {
        // Prep posting
        $poll = $uid = $bitfield = $options = '';
        $allow_bbcode = $allow_urls = $allow_smilies = true;
        generate_text_for_storage($response, $uid, $bitfield, $options, $allow_bbcode, $allow_urls, $allow_smilies);

        $data = array(
            'poster_id'             => $job['ailabs_user_id'],
            // General Posting Settings
            'forum_id'              => $job['forum_id'], // The forum ID in which the post will be placed. (int)
            'topic_id'              => $job['topic_id'], // Post a new topic or in an existing one? Set to 0 to create a new one, if not, specify your topic ID here instead.
            'icon_id'               => false, // The Icon ID in which the post will be displayed with on the viewforum, set to false for icon_id. (int)
            // Defining Post Options
            'enable_bbcode'         => true, // Enable BBcode in this post. (bool)
            'enable_smilies'        => true, // Enabe smilies in this post. (bool)
            'enable_urls'           => true, // Enable self-parsing URL links in this post. (bool)
            'enable_sig'            => true, // Enable the signature of the poster to be displayed in the post. (bool)
            // Message Body
            'message'               => $response, // Your text you wish to have submitted. It should pass through generate_text_for_storage() before this. (string)
            'message_md5'           => md5($response), // The md5 hash of your message
            'post_checksum'         => md5($response), // The md5 hash of your message
            // Values from generate_text_for_storage()
            'bbcode_bitfield'       => $bitfield, // Value created from the generate_text_for_storage() function.
            'bbcode_uid'            => $uid, // Value created from the generate_text_for_storage() function.    
            // Other Options
            'post_edit_locked'      => 0, // Disallow post editing? 1 = Yes, 0 = No
            'topic_title'           => $job['post_subject'],
            'notify_set'            => true, // (bool)
            'notify'                => true, // (bool)
            'post_time'             => 0, // Set a specific time, use 0 to let submit_post() take care of getting the proper time (int)
            'forum_name'            => $job['forum_name'], // For identifying the name of the forum in a notification email. (string)    // Indexing
            'enable_indexing'       => true, // Allow indexing the post? (bool)    // 3.0.6
        );

        // Post as designated user and then switch back to original one
        $actual_user_id = $this->user->data['user_id'];
        $this->switch_user($job['ailabs_user_id']);
        $post_subject = ((strpos($job['post_subject'], 'Re: ') !== 0) ? 'Re: ' : '') . censor_text($job['post_subject']);

        include($this->root_path . 'includes/functions_posting.' . $this->php_ext);
        submit_post('reply', $post_subject, $job['ailabs_username'], POST_NORMAL, $poll, $data);

        $this->switch_user($actual_user_id);

        return $data;
    }

    /**
     * Switch to the AI Labs user
     * @param int $new_user_id
     * @return bool
     */
    protected function switch_user($new_user_id)
    {
        if ($this->user->data['user_id'] == $new_user_id) {
            // Nothing to do
            return true;
        }

        $sql = 'SELECT * FROM ' . USERS_TABLE . ' WHERE user_id = ' . (int) $new_user_id;
        $result = $this->db->sql_query($sql);
        $row = $this->db->sql_fetchrow($result);
        $this->db->sql_freeresult($result);

        $row['is_registered'] = true;
        $this->user->data = array_merge($this->user->data, $row);
        $this->auth->acl($this->user->data);

        return true;
    }

    /*
        Inspired by https://www.phpbb.com/community/viewtopic.php?t=2556226
    */
    protected function attach_to_post($isUrl, $urlOrFilename, $postId, $topicId = 0, $forumId = 0, $userId = 0, $realFileName = '', $comment = '', $dispatchEvent = true)
    {
        if (
            getType($isUrl) != 'boolean' ||
            getType($urlOrFilename) != 'string' ||
            getType($postId) != 'integer' ||
            getType($comment) != 'string' ||
            getType($dispatchEvent) != 'boolean' ||
            getType($topicId) != 'integer' ||
            getType($forumId) != 'integer' ||
            getType($userId) != 'integer'
        )
            throw 'Type Missmatch';

        if ($postId <= 0)
            throw 'Post ID cannot be zero!';

        // if not given, get missing IDs
        if ($topicId == 0 || $forumId == 0 || $userId == 0) {
            $idRow = $this->db->sql_fetchrow($this->db->sql_query('SELECT post_id, topic_id, forum_id, poster_id FROM ' . POSTS_TABLE . ' WHERE post_id = ' . $postId));
            if (!$idRow)
                return ['POST_NOT_EXISTS'];
            $topicId = intval($idRow['topic_id']);
            $forumId = intval($idRow['topic_id']);
            $userId = intval($idRow['poster_id']);
        }

        // get required classes
        $upload = $this->phpbb_container->get('files.upload');
        $cache = $this->phpbb_container->get('cache');
        $attach = $this->phpbb_container->get('attachment.upload');

        // load file from remote location to local server
        $upload->set_disallowed_content([]);
        $extensions = $cache->obtain_attach_extensions($forumId);
        $extensionArr = array_keys($extensions['_allowed_']);
        $upload->set_allowed_extensions($extensionArr);

        $tempFile = null;
        if ($isUrl) {
            $upload->set_allowed_extensions($extensionArr);
            $tempFile = $upload->handle_upload('files.types.remote', $urlOrFilename);
        } else {
            // TODO: There seems to be some kind of bug where phpBB unable to get extension for local file
            array_push($extensionArr, '');
            $upload->set_allowed_extensions($extensionArr);
            $local_filedata = array();
            $tempFile = $upload->handle_upload('files.types.local', $urlOrFilename, $local_filedata);
        }

        if (count($tempFile->error) > 0) {
            $ext = '.';
            if (strrpos($urlOrFilename, '.') !== false)
                $ext = substr($urlOrFilename, strrpos($urlOrFilename, '.') + 1);
            if ($tempFile->error[0] == 'URL_INVALID' && !in_array($ext, $extensionArr))
                return ['FILE_EXTENSION_NOT_ALLOWED', 'EXTENSION=.' . htmlspecialchars($ext)];
            return $tempFile->error;
        }

        $realFileNameExt = $isUrl ? '.' . $tempFile->get('extension') : '';

        $realFileName = $realFileName == '' ? $tempFile->get('realname') : htmlspecialchars($realFileName) . $realFileNameExt;

        $tempFileData = [
            'realname' => $realFileName,
            'size' => $tempFile->get('filesize'),
            'type' => $tempFile->get('mimetype'),
        ];

        // create attachment from temp file
        if (!function_exists('create_thumbnail'))
            require($this->root_path . 'includes/functions_posting.php');

        $attachFileName = $isUrl ? $tempFile->get('filename') : $urlOrFilename;

        $attachmentFileData = $attach->upload('', $forumId, true, $attachFileName, false, $tempFileData);

        if (!$attachmentFileData['post_attach'])
            return ['FILE_ATTACH_ERROR', $realFileName, $attachFileName, $tempFileData];

        if (count($attachmentFileData['error']) > 0)
            return $attachmentFileData['error'];

        $sql_ary = array(
            'physical_filename'     => $attachmentFileData['physical_filename'],
            'attach_comment'        => $comment,
            'real_filename'         => $attachmentFileData['real_filename'],
            'extension'             => $attachmentFileData['extension'],
            'mimetype'              => $attachmentFileData['mimetype'],
            'filesize'              => $attachmentFileData['filesize'],
            'filetime'              => $attachmentFileData['filetime'],
            'thumbnail'             => $attachmentFileData['thumbnail'],
            'is_orphan'             => 0,
            'in_message'            => 0,
            'poster_id'             => $userId,
            'post_msg_id'           => $postId,
            'topic_id'              => $topicId,
        );

        if ($dispatchEvent) {
            $dispatcher = $this->phpbb_container->get('dispatcher');
            $vars = array('sql_ary');
            extract($dispatcher->trigger_event('core.modify_attachment_sql_ary_on_submit', compact($vars)));
        }

        $this->db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary));
        $newAttachmentID = intval($this->db->sql_nextid());

        if ($newAttachmentID == 0)
            return ['SQL_ATTACHMENT_INSERT_ERROR'];

        $this->db->sql_query('UPDATE ' . POSTS_TABLE . ' SET post_attachment = 1 WHERE post_id = ' . $postId);

        return ['SUCCESS', $newAttachmentID, $postId];
    }

    protected function image_filename($ind)
    {
        return 'ailabs_' . $this->job['ailabs_user_id'] . '_' . $this->job_id . '_' . $ind;
    }

    protected function save_base64_to_temp_file($base64, $ind, $ext = '.png')
    {
        $temp_dir_path = sys_get_temp_dir();

        if (substr($temp_dir_path, -1) != DIRECTORY_SEPARATOR)
            $temp_dir_path .= DIRECTORY_SEPARATOR;

        $filename = $temp_dir_path . $this->image_filename($ind) . $ext;

        $handle = fopen($filename, 'wb');
        fwrite($handle, base64_decode($base64));
        fclose($handle);

        return $filename;
    }
}