<?php

require './vendor/autoload.php';
require './Autoloader.php';

error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED & ~E_STRICT & ~E_NOTICE & ~E_WARNING);

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Medoo\Medoo;

class WallSocket implements MessageComponentInterface {

    /**
     * @var SplObjectStorage
     */
    protected $clients;

    /**
     * @var array
     */
    protected $users;
    /**
     * @var array
     */
    protected $guestUsers;
    /**
     * @var Medoo
     */
    protected $db;

    protected $dispatcher;
    protected $groupChatClients;
    protected $globalLang = [];
    protected $ws_config = [];

    const ROLE_TYPE_FOLLOWER = 1;
    const ROLE_TYPE_EDITOR = 2;
    const ROLE_TYPE_MODERATOR = 3;


    public function __construct($ws_config) {
        error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);

        $this->ws_config = $ws_config;
        $this->db = new Medoo($ws_config["db_config"]);
        $this->clients = new \SplObjectStorage;
        $this->users = [];

        require_once('./engine/data/config.php');
        require_once('./engine/eventSubscribers/Events/EventDispatcher.php');

        define('ENGINE_DIR', './engine');

        // Создаем подписчик событий
        $this->dispatcher = new EventDispatcher($config, $this->db);
        $dispatcher = $this->dispatcher;
        require_once('./engine/mods/event_subscribers.php');

    }



    public function onOpen(ConnectionInterface $conn) {
        $this->db = new Medoo($this->ws_config["db_config"]);

        parse_str($conn->httpRequest->getUri()->getQuery(), $query);
        $token = $query['usr_token'] ?? null;
        if (empty($token)) {
            $this->log_message("Подключение... токен отсутсвует\n");
        } else {
            $this->log_message("Подключение... токен $token \n");


            $userId = $this->authenticateUser($token);

            if ($userId) {
                $this->updateUserStatus($userId, "online");

                $conn->userId = $userId;
                $this->users[$userId] = $conn;
                $this->clients->attach($conn);

                $this->log_message("[{$userId}] Подключен\n");
            } else {
                $this->guestUsers[] = $conn;
            }
        }

    }

    private function updateUserStatus($user_id, $status) {
        // Обновление статуса в базе данных
        $this->db->update("users", ["status" => $status, "lastdate" => date("Y-m-d H:i:s")], ["id" => $user_id]);
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        date_default_timezone_set('UTC');
        $this->db = new Medoo($this->ws_config["db_config"]);

        $messageData = json_decode($msg, true);
        $connectedUserData = $this->db->get("users", "*", ["id" => $from->userId]) ?? [];

        if ($messageData["type"] == "wall") {
                $token = $this->db->get("tokens", "token", ["user_id" => $from->userId]);
                $wall = $this->db->get("walls", "*", ["id" => $messageData["wallId"]]);

                if ($messageData["action"] === "connect") {

                    if (!empty($messageData["wallId"])) {

                        $wall = $this->db->get("walls", "*", ["id" => $messageData["wallId"]]);
                        if (!empty($wall)) {

                            $wallFollowers = $this->db->select("wall_followers", "*", ["wall_id" => $wall["id"]]);
                            $wallPosts = $this->db->select("posts", "*", [
                                "wall_id" => $wall["id"],
                                "ORDER" => ["created_at" => "DESC"],
                                "LIMIT" => [0, 5]
                            ]);
                            $allComments = [];

                            foreach ($wallPosts as &$post) {
                                $post["author_data"] = $this->db->get("users", "*", ["id" => $post["author_id"]]) ?? [];

                                if ($wall["owner_id"] == $post["author_id"]) {
                                    $post["author_data"]["is_owner"] = true;
                                } else {
                                    $post["author_data"]["is_owner"] = false;
                                }

                                $post["media"] = $this->db->get("post_media", "*", ["post_id" => $post["id"]]) ?? [];
                                $post["comments"][$post["id"]] = $this->getCommentsTreeMedoo($post["id"], "wall_post");

                                $post["likes"] = $this->db->count("likes", "*", ["post_id" => $post["id"]]) ?? 0;
                                $post["commentsCount"] = $this->db->count("comments", "*", ["entity_id" => $post["id"], "entity_type" => "wall_post"]) ?? 0;
                            }

                            $wallPosts = array_reverse($wallPosts);

                            $isSubscribed = !empty($this->db->get("wall_followers", "*", ["user_id" => $from->userId, "wall_id" => $wall["id"]]));

                            foreach ($this->users as $recipientConn) {

                                $recipientConn->send(json_encode([
                                    'type' => $messageData["type"],
                                    'action' => $messageData["action"],
                                    'wallData' => $wall,
                                    'wallFollowers' => $wallFollowers,
                                    'wallPosts' => $wallPosts,
                                    'isSubscribed' => $isSubscribed,
                                    'currentUserToken' => $token,
                                    'wallId' => $messageData["wallId"]
                                ]));

                            }

                        }

                    }

                }

                if (!empty($from->userId)) {

                    if ($messageData["action"] === "subscribeUnsubscribe") {

                        $isSubscribed = false;
                        if ($messageData["isSubscribed"] === false) {
                            $this->db->insert("wall_followers", [
                                "wall_id" => $messageData["wallId"],
                                "user_id" => $from->userId,
                                "role_type" => self::ROLE_TYPE_FOLLOWER,
                                "created_at" => date("Y-m-d H:i:s")
                            ]);
                            $isSubscribed = true;
                        } else {
                            $this->db->delete("wall_followers", [
                                "user_id" => $from->userId,
                                "wall_id" => $messageData["wallId"],
                            ]);
                        }

                        $wallFollowersCount = $this->db->count("wall_followers", "user_id", ["wall_id" => $messageData["wallId"]]);

                        foreach ($this->users as $recipientConn) {

                            $recipientConn->send(json_encode([
                                'type' => $messageData["type"],
                                'action' => $messageData["action"],
                                'currentUserToken' => $token,
                                'wallFollowersCount' => $wallFollowersCount,
                                'isSubscribed' => $isSubscribed,
                                'wallId' => $messageData["wallId"]
                            ]));

                        }

                        foreach ($this->guestUsers as $guestRecipientConn) {

                            $guestRecipientConn->send(json_encode([
                                'type' => $messageData["type"],
                                'action' => $messageData["action"],
                                'currentUserToken' => $token,
                                'wallFollowersCount' => $wallFollowersCount,
                                'isSubscribed' => $isSubscribed,
                                'wallId' => $messageData["wallId"]
                            ]));
                        }

                } else if ($messageData["action"] === "createPost") {

                    $isAccess = true;

                    if ($wall["posting_access_type"] === 2) {
                        $existsFollowing = $this->db->get("wall_followers", "*", [
                            "wall_id" => $messageData["wallId"],
                            "user_id" => $from->userId,
                        ]) ?? false;

                        if (!$existsFollowing && $wall["owner_id"] != $from->userId) {
                            $this->sendErrorToUser($from, $messageData ,"Follow where create posts");
                            $isAccess = false;
                        }

                    } else if ($wall["posting_access_type"] === 3) {

                        if ($wall["owner_id"] != $from->userId) {
                            $this->sendErrorToUser($from, $messageData ,"You are not allowed to create posts");
                            $isAccess = false;
                        }

                    }

                    if ($isAccess) {
                        $uploadDir = __DIR__ . '/uploads/files/';
                        if (!file_exists($uploadDir)) mkdir($uploadDir, 0755, true);

                        $savedFiles = [];

                        foreach ($messageData['file'] as $file) {

                            if (preg_match('/^data:(.*);base64,(.*)$/', $file['data'], $matches)) {
                                $mimeType = $matches[1];
                                $base64 = $matches[2];
                                $extension = explode('/', $mimeType)[1];
                                $binaryData = base64_decode($base64);

                                $filename = uniqid() . '.' . $extension;
                                $path = $uploadDir . $filename;
                                file_put_contents($path, $binaryData);

                                $savedFiles[] = [
                                    'url' => 'engine/websockets/uploads/files/' . $filename,
                                    'type' => $file['type']
                                ];
                            }
                        }

                        $this->db->insert("posts", [
                            "wall_id" => $messageData["wallId"],
                            "author_id" => $from->userId,
                            "content" => $messageData['content'],
                            "post_type" => 4,
                            "status_type" => 1,
                            "created_at" => date("Y-m-d H:i:s")
                        ]);

                        $post = $this->db->get("posts", "*", ["id" => $this->db->id()]) ?? [];

                        foreach ($savedFiles as $savedFile) {
                            $this->db->insert("post_media", [
                                "post_id" => $post["id"],
                                "media_type" => 1,
                                "url" => $savedFile['url'],
                                "thumbnail_url" => $savedFile['url']
                            ]);
                            $post["media"] = $this->db->get("post_media", "*", ["id" => $this->db->id()]) ?? [];
                        }

                        $post["comments"] = [];
                        $post["author_data"] = $this->db->get("users", "*", ["id" => $from->userId]) ?? [];

                        foreach ($this->users as $recipientConn) {

                            $recipientConn->send(json_encode([
                                'type' => $messageData["type"],
                                'action' => $messageData["action"],
                                'currentUserToken' => $token,
                                'post' => $post,
                                'wallId' => $messageData["wallId"]
                            ]));

                        }

                        foreach ($this->guestUsers as $guestRecipientConn) {

                            $guestRecipientConn->send(json_encode([
                                'type' => $messageData["type"],
                                'action' => $messageData["action"],
                                'currentUserToken' => $token,
                                'post' => $post,
                                'wallId' => $messageData["wallId"]
                            ]));

                        }

                    }

                } else if ($messageData["action"] === "comment") {

                    $post = $this->db->get("posts", "*", ["id" => $messageData["postId"]]) ?? [];

                    $this->db->insert("comments", [
                        "user_id" => $from->userId,
                        "author" => $this->db->get("users", "name", ["id" => $from->userId]) ?? "",
                        "entity_id" => $post["id"],
                        "entity_type" => "wall_post",
                        "comment_text" => $messageData['text'],
                        "created_at" => date("Y-m-d H:i:s")
                    ]);

                    $comment = $this->db->get("comments", "*", ["id" => $this->db->id()]) ?? [];

                    $commentsCount = $this->db->count("comments", "*", ["entity_id" => $messageData["postId"], "entity_type" => "wall_post"]);

                    $post["comments"] = $this->getCommentsTreeMedoo($post["id"], "wall_post");

                    foreach ($this->users as $recipientConn) {

                        $recipientConn->send(json_encode([
                            'type' => $messageData["type"],
                            'action' => $messageData["action"],
                            'currentUserToken' => $token,
                            'postId' => $post["id"],
                            'comment' => $comment,
                            'post' => $post,
                            'commentsCount' => $commentsCount
                        ]));

                    }

                    foreach ($this->guestUsers as $guestRecipientConn) {

                        $guestRecipientConn->send(json_encode([
                            'type' => $messageData["type"],
                            'action' => $messageData["action"],
                            'currentUserToken' => $token,
                            'postId' => $post["id"],
                            'comment' => $comment,
                            'post' => $post,
                            'commentsCount' => $commentsCount
                        ]));

                    }

                } else if ($messageData["action"] === "likeUnlike") {

                    $likeExists = $this->db->get("likes", "*", ["post_id" => $messageData["postId"], "user_id" => $from->userId]);

                    if (!empty($likeExists)) {
                        $this->db->delete("likes", [
                            "post_id" => $messageData["postId"],
                            "user_id" => $from->userId,
                        ]);
                    } else {
                        $this->db->insert("likes", [
                            "post_id" => $messageData["postId"],
                            "user_id" => $from->userId,
                        ]);
                    }

                    $likesCount = $this->db->count("likes", "*", ["post_id" => $messageData["postId"]]);

                    foreach ($this->users as $recipientConn) {

                        $recipientConn->send(json_encode([
                            'type' => $messageData["type"],
                            'action' => $messageData["action"],
                            'currentUserToken' => $token,
                            'postId' => $messageData["postId"],
                            'likes' => $likesCount
                        ]));

                    }

                    foreach ($this->guestUsers as $guestRecipientConn) {

                        $guestRecipientConn->send(json_encode([
                            'type' => $messageData["type"],
                            'action' => $messageData["action"],
                            'currentUserToken' => $token,
                            'postId' => $messageData["postId"],
                            'likes' => $likesCount
                        ]));

                    }


                } else if ($messageData["action"] === "replyComment") {


                    $post = $this->db->get("posts", "*", ["id" => $messageData["postId"]]) ?? [];

                    $this->db->insert("comments", [
                        "user_id" => $from->userId,
                        "author" => $this->db->get("users", "name", ["id" => $from->userId]) ?? "",
                        "entity_id" => $post["id"],
                        "entity_type" => "wall_post",
                        "comment_text" => $messageData['replyText'],
                        "created_at" => date("Y-m-d H:i:s"),
                        "parent_id" => $messageData["parentId"]
                    ]);

                    $comment = $this->db->get("comments", "*", ["id" => $this->db->id()]) ?? [];
                    $commentsCount = $this->db->count("comments", "*", ["entity_id" => $messageData["postId"], "entity_type" => "wall_post"]);

                    $post["comments"] = $this->getCommentsTreeMedoo($post["id"], "wall_post");

                    foreach ($this->users as $recipientConn) {

                        $recipientConn->send(json_encode([
                            'type' => $messageData["type"],
                            'action' => $messageData["action"],
                            'currentUserToken' => $token,
                            'postId' => $post["id"],
                            'comment' => $comment,
                            'post' => $post,
                            'commentsCount' => $commentsCount
                        ]));

                    }

                    foreach ($this->guestUsers as $guestRecipientConn) {

                        $guestRecipientConn->send(json_encode([
                            'type' => $messageData["type"],
                            'action' => $messageData["action"],
                            'currentUserToken' => $token,
                            'postId' => $post["id"],
                            'comment' => $comment,
                            'post' => $post,
                            'commentsCount' => $commentsCount
                        ]));

                    }

                }

            } else {
                $from->send(json_encode([
                    'type' => $messageData["type"],
                    'action' => $messageData["action"],
                    'wallData' => $wall,
                    'wallFollowers' => $wallFollowers ?? [],
                    'wallPosts' => $wallPosts ?? [],
                    'isSubscribed' => $isSubscribed ?? [],
                    'currentUserToken' => $token,
                    'wallId' => $messageData["wallId"]
                ]));

                if ($messageData["action"] === "likeUnlike") {
                    $this->sendErrorToUser($from, $messageData, "Authorize to like unlike post");
                } else if ($messageData["action"] === "replyComment") {
                    $this->sendErrorToUser($from, $messageData, "Authorize to reply comment");
                } else if ($messageData["action"] === "comment") {
                    $this->sendErrorToUser($from, $messageData, "Authorize to comment post");
                }
            }


        }

    }


    function sendNotification($from, $toUserId, $moduleUrl, $content, $prefix, $type ,$svg)
    {
        $this->db->insert("notifications", [
            "user_id" => $toUserId,
            "sender_id" => $from->userId,
            "module_id" => 0,
            "module_type" => $type,
            "language" => "RU",
            "created_at" => date("Y-m-d H:i:s"),
            "module_url" => $moduleUrl,
            "content" => $content,
            "status" => 0,
            "svg_ico" => $svg
        ]);

        if (!empty($this->users[$toUserId."_notification"])) {
            $recipientConn = $this->users[$toUserId."_notification"];
            $notifications = $this->db->select("notifications", "*", ["user_id" => $toUserId]) ?? null;

            $notifications = array_reverse($notifications);

            foreach ($notifications as &$notification) {
                $notification["content"] = preg_replace_callback('/%%(.*?)%%/', function ($matches) {
                    $map = $this->globalLang;
                    return $map[$matches[1]] ?? $matches[0]; // если не найдено — оставить как есть
                }, $notification["content"]);

            }
            unset($notification);

            $recipientConn->send(json_encode([
                'type' => "notification",
                'action' => "send",
                'notifications' => $notifications,
            ]));
        }
    }

    public function onClose(ConnectionInterface $conn) {
        $user_id = $conn->userId;

        $this->updateUserStatus($user_id, "offline");

        unset($this->users[$user_id."_pm"]);
        unset($this->users[$user_id."_ticket_conn"]);

        if (!empty($this->groupChatClients)) {
            unset($this->groupChatClients[1][$user_id]);
            unset($this->users[$user_id."_group_chat"]);

            $response = [
                "type" => "live-chat",
                "action" => "online-count",
                "count" => count($this->groupChatClients[1])
            ];
            foreach ($this->groupChatClients as $groupId => $chatClients) {
                if ($groupId == 1) {
                    foreach ($chatClients as $chatClientID) {
                        $recipientConn = $this->users[$chatClientID."_group_chat"];

                        $recipientConn->send(json_encode($response));

                    }
                }
            }
        }


        $this->clients->detach($conn);
        $this->log_message("Подключение закрыто ({$conn->resourceId})\n");
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "Ошибка: {$e->getMessage()}\n";
        $conn->close();
    }

    private function authenticateUser($token) {

        $userId = $this->db->get("tokens", "user_id", ["token" => $token]);

        if ($userId) {
            $validTokens = [];
            $validTokens[$token] = $userId;
        }

        return $validTokens[$token] ?? null;
    }

    function log_message($message, $level = 'INFO') {
        $timestamp = date("Y-m-d H:i:s");
        $formatted_message = "[$timestamp] [$level] $message\n";

        file_put_contents('main.log', $formatted_message, FILE_APPEND);
    }


    private function sendErrorToUser($from, $messageData, $errorMessage)
    {

        $from->send(json_encode([
            'type' => $messageData["type"],
            'action' => "error",
            'currentUserToken' => $messageData["from_user_token"],
            'errorMessage' => $errorMessage,
            'wallId' => $messageData["wallId"]
        ]));

    }

    function getCommentsTreeMedoo($entityId, $entityType = 'wall_post', $parentId = null)
    {
        $comments = $this->db->select("comments", "*", [
            "AND" => [
                "entity_id" => $entityId,
                "entity_type" => $entityType,
                "parent_id" => $parentId
            ],
            "ORDER" => ["created_at" => "ASC"]
        ]);

        $result = [];

        foreach ($comments as $comment) {
            $result[] = [
                "id" => (int)$comment["id"],
                "parentId" => $comment["parent_id"] !== null ? (int)$comment["parent_id"] : null,
                "author" => $comment["author"],
                "date" => $this->formatTimeAgo($comment["created_at"]),
                "text" => $comment["comment_text"],
                "avatar" => $this->db->get("users", "avatar", ["id" => $comment["user_id"]]),
                "replies" => $this->getCommentsTreeMedoo($entityId, $entityType, $comment["id"])
            ];
        }

        return $result;
    }

    private function formatTimeAgo($datetime)
    {
        $time = strtotime($datetime);
        $diff = time() - $time;

        if ($diff < 60) return $diff . ' секунд назад';
        if ($diff < 3600) return floor($diff / 60) . ' минут назад';
        if ($diff < 86400) return floor($diff / 3600) . ' часов назад';

        return date('Y-m-d H:i', $time); // fallback
    }



}

require_once "./engine/data/ws_config.php";

// Запуск сервера
$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new WallSocket($ws_config)
        )
    ),
    8081,
    $ws_config["ip_address"],
);

echo "Сервер для лайв стены запущен на: ws://" . $ws_config["ip_address"] . ":8081\n";
$server->run();
?>

