<?php

namespace FSPoster\App\SocialNetworks\Twitter\App;

use Exception;
use FSPoster\App\Providers\Channels\ChannelSessionException;
use FSPoster\App\Providers\Core\Container;
use FSPoster\App\Providers\Core\Settings;
use FSPoster\App\Providers\Helpers\Date;
use FSPoster\App\Providers\Helpers\Helper;
use FSPoster\App\SocialNetworks\Twitter\App\Repositories\TwitterCommentRepository;
use FSPoster\App\SocialNetworks\Twitter\Api\AppMethod\Api as AppMethodApi;
use FSPoster\App\SocialNetworks\Twitter\Api\AppMethod\AuthData as AppMethodAuthData;
use FSPoster\App\SocialNetworks\Twitter\Api\CookieMethod\Api as CookieMethodApi;
use FSPoster\App\SocialNetworks\Twitter\Api\CookieMethod\AuthData as CookieMethodAuthData;
use FSPoster\App\SocialNetworks\Twitter\Api\CookieMethod\WorkerCredentialsDTO;
use Throwable;

class CommentService
{
    private array $twitterIdToWpId = [];
    private bool $commentFloodCheckerBypassed = false;
    private ?string $commentAuthorIP = null;
    private TwitterCommentRepository $repository;

    public function __construct(
        TwitterCommentRepository $repository
    ) {
        $this->repository = $repository;
    }

    public function init(): void
    {
        $importCommentSettings = Settings::get('twitter_import_comments', false);

        if (!$importCommentSettings) {
            return;
        }

        $lastRunOn = Settings::get('twitter_cron_job_run_on', 0);
        $lastRunOn = is_numeric($lastRunOn) ? (int)$lastRunOn : 0;
        $diff = Date::epoch() - $lastRunOn;

        if ($diff > 3 * 60 * 60) {
            Settings::set('twitter_cron_job_run_on', Date::epoch(), false, false);

            try {
                $this->fetchTwitterComments();
            } catch (Throwable $e) {
            }
        }
    }

    private function fetchTwitterComments(): void
    {
        $allBlogs = Helper::getBlogs();

        foreach ($allBlogs as $blogId) {
            Helper::setBlogId($blogId);

            try {
                $this->fetchTwitterCommentsForCurrentBlog();
            } catch (Exception $e) {
            }

            Helper::resetBlogId();
        }
    }

    /**
     * @throws ChannelSessionException
     */
    private function fetchTwitterCommentsForCurrentBlog(): void
    {
        $posts = $this->getPostsToSyncComments();

        foreach ($posts as $post) {
            if (empty($post['remote_post_id'])) {
                continue;
            }

            $channel = $this->repository->findChannelById((int)$post['channel_id']);

            if (!$channel) {
                continue;
            }
            $channelSession = $channel->channel_session->fetch();

            if (!$channelSession || !in_array($channelSession->method, ['app', 'cookie'], true)) {
                continue;
            }

            $since = $this->repository->getLastCommentFetchDate((int)$post['wp_post_id'], (int)$post['id']);
            $since = empty($since) || is_numeric($since) ? $since : Date::epoch($since);

            $fetchedAt = Date::epoch();

            try {
                $comments = $this->fetchCommentsForChannelSession($channelSession, (string)$post['remote_post_id'], $since);
            } catch (Throwable $e) {
                continue;
            }

            if (empty($comments)) {
                $this->repository->updateLastCommentFetchDate((int)$post['wp_post_id'], (int)$post['id'], $fetchedAt);
                continue;
            }

            $batchTwitterIds = [];

            foreach ($comments as $comment) {
                $commentId = (string)($comment['id'] ?? '');

                if (!empty($commentId) && !isset($this->twitterIdToWpId[$commentId])) {
                    $batchTwitterIds[] = $commentId;
                }
            }

            if (!empty($batchTwitterIds)) {
                $existingRows = $this->repository->findWpCommentIdsByTwitterCommentIds(array_values(array_unique($batchTwitterIds)));

                foreach ($existingRows as $row) {
                    $this->twitterIdToWpId[(string)$row['meta_value']] = (int)$row['comment_id'];
                }
            }

            foreach ($comments as $comment) {
                $commentId = (string)($comment['id'] ?? '');

                if (empty($commentId) || isset($this->twitterIdToWpId[$commentId])) {
                    continue;
                }

                try {
                    $this->insertComment((int)$post['wp_post_id'], $commentId, (string)($comment['parent_id'] ?? ''), (string)($comment['text'] ?? ''), (string)($comment['author_name'] ?? ''), (string)($comment['author_username'] ?? ''), (string)($comment['created_at'] ?? ''));
                } catch (Throwable $e) {
                }
            }

            $this->repository->updateLastCommentFetchDate((int)$post['wp_post_id'], (int)$post['id'], $fetchedAt);
        }
    }

    private function getPostsToSyncComments(): array
    {
        $postTypes = $this->getCommentablePostTypes();

        if (empty($postTypes)) {
            return [];
        }

        $channels = $this->repository->getActiveTwitterChannels();

        if (empty($channels)) {
            return [];
        }

        $channelIds = array_map(static fn($channel) => $channel->id, $channels);
        $sendTimeFrom = Date::dateTimeSQL('now', '-7 days');

        return $this->repository->getRecentSuccessfulSchedules($channelIds, $postTypes, $sendTimeFrom);
    }

    private function getCommentablePostTypes(): array
    {
        global $_wp_post_type_features;

        $postTypes = [];

        foreach ($_wp_post_type_features as $postType => $info) {
            if (isset($info['comments']) && $info['comments']) {
                $postTypes[] = $postType;
            }
        }

        return $postTypes;
    }

    /**
     * @throws ChannelSessionException
     * @throws \ReflectionException
     */
    private function fetchCommentsForChannelSession($channelSession, string $postId, $since): array
    {
        $authDataArray = $channelSession->data_obj->auth_data ?? [];

        if ($channelSession->method === 'app') {
            $authData = Container::get(AppMethodAuthData::class);
            $authData->setFromArray($authDataArray);

            $api = Container::get(AppMethodApi::class);
            $api->setProxy($channelSession->proxy)->setAuthException(ChannelSessionException::class)->setAuthData($authData);

            return $api->fetchComments($postId, $since);
        }

        $authData = Container::get(CookieMethodAuthData::class);
        $authData->setFromArray($authDataArray);

        $workerCredentials = Container::get(WorkerCredentialsDTO::class);

        $api = Container::get(CookieMethodApi::class);
        $api->setProxy($channelSession->proxy)->setAuthException(ChannelSessionException::class)->setAuthData($authData)->initWorker($workerCredentials);

        return $api->fetchComments($postId, $since);
    }

    private function insertComment(int $postId, string $twitterCommentId, string $twitterCommentParentId, string $text, string $authorName = '', string $authorUsername = '', string $commentDate = ''): void
    {
        $authorUrl = empty($authorUsername) ? '' : 'https://twitter.com/' . $authorUsername;
        $commentApproved = empty(trim($text)) ? 0 : 1;

        $commentData = ['comment_post_ID' => $postId, 'comment_content' => $text, 'comment_author' => $authorName, 'comment_agent' => sprintf('FS Poster (twitter:%s)', $twitterCommentId), 'comment_author_url' => $authorUrl, 'comment_author_email' => get_option('admin_email'), 'comment_date' => empty($commentDate) ? Date::dateTimeSQL() : Date::dateTimeSQL($commentDate), 'comment_approved' => $commentApproved, 'comment_author_IP' => $this->getCommentAuthorIP(),];

        if (!empty($twitterCommentParentId)) {

            if (isset($this->twitterIdToWpId[$twitterCommentParentId])) {
                $parentWpId = $this->twitterIdToWpId[$twitterCommentParentId];
            } else {
                $parentWpId = $this->repository->findWpCommentIdByTwitterParentId($postId, $twitterCommentParentId);
            }

            if (!empty($parentWpId)) {
                $this->twitterIdToWpId[$twitterCommentParentId] = $parentWpId;
                $commentData['comment_parent'] = $parentWpId;
            }
        }

        $this->bypassCommentFloodChecker();

        $insertId = $this->repository->insertWpComment($commentData);

        if ($insertId === null) {
            return;
        }

        $this->twitterIdToWpId[$twitterCommentId] = $insertId;
        $this->repository->addWpCommentMeta($insertId, $twitterCommentId);
    }

    private function getCommentAuthorIP(): string
    {
        if (is_null($this->commentAuthorIP)) {
            $this->commentAuthorIP = mt_rand(1, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(1, 254);
        }

        return $this->commentAuthorIP;
    }

    private function bypassCommentFloodChecker(): void
    {
        if ($this->commentFloodCheckerBypassed) {
            return;
        }

        add_filter('wp_is_comment_flood', function ($isFlood, $ip) {
            if ($ip === $this->getCommentAuthorIP()) {
                return true;
            }

            return $isFlood;
        }, 1, 2);

        add_filter('wp_is_comment_flood', function ($isFlood, $ip) {
            if ($ip === $this->getCommentAuthorIP()) {
                return false;
            }

            return $isFlood;
        }, 9999, 2);

        $this->commentFloodCheckerBypassed = true;
    }
}
