<?php

namespace BitApps\SocialPro\HTTP\Services\Social\ThreadsService;

use AllowDynamicProperties;
use BitApps\Social\Config;
use BitApps\Social\Deps\BitApps\WPKit\Hooks\Hooks;
use BitApps\Social\HTTP\Services\Interfaces\SocialInterface;
use BitApps\Social\HTTP\Services\Traits\LoggerTrait;
use BitApps\Social\Model\Schedule;
use BitApps\Social\Utils\Common;
use BitApps\SocialPro\Deps\BitApps\WPKit\Http\Client\HttpClient;

#[AllowDynamicProperties]
class PostPublishThreadsService implements SocialInterface
{
    use Common, LoggerTrait;

    private const TOPIC_CHARACTER_LIMIT = 50;

    private const MEDIA_READY_MAX_RETRIES = 20;

    private const MEDIA_READY_RETRY_SLEEP_SECONDS = 3;

    public $baseUrl = 'https://graph.threads.net/v1.0/';

    public $userName;

    private $postUrl = 'https://www.threads.com';

    private $httpClient;

    private $accessToken;

    private $mediaUploadErrors = [];

    private $commentError = [];

    private $accountId;

    private $threadsData = [];

    public function __construct()
    {
        $this->httpClient = new HttpClient();
    }

    public function publishPost($data)
    {
        $post = $data['post'] ?? null;
        $template = (object) $data['template'];
        $scheduleType = $data['schedule_type'] ?? null;
        $accountDetails = $data['account_details'];
        $schedule_id = $data['schedule_id'] ?? null;
        $accountId = $accountDetails->account_id;
        $accountName = $accountDetails->account_name;

        $tokenService = new ThreadsRefreshTokenService($accountDetails);

        $this->accessToken = $tokenService->accessToken();
        $this->accountId = $accountDetails->account_id;

        $postData = $this->postData($scheduleType, $post, $template);
        $this->threadsData['post'] = $this->normalizePostData($postData, true);

        $response = $this->createPost($postData);

        if (isset($response->shortcode)) {
            $postUrl = "{$this->postUrl}/post/{$response->shortcode}";
        }

        $logData = [
            'schedule_id' => $schedule_id,
            'details'     => [
                'account_id'   => $accountId,
                'account_name' => $accountName,
                'post_id'      => $post['ID'] ?? null,
                'response'     => $response,
                'post_url'     => $postUrl ?? null,
            ],
            'platform' => 'threads',
            'status'   => \is_object($response) && property_exists($response, 'id') ? 1 : 0
        ];

        if (\count($this->mediaUploadErrors)) {
            $logData['details']['imageUploadErrors'] = $this->mediaUploadErrors;
        }
        if (\count($this->commentError)) {
            $logData['details']['commentError'] = $this->commentError;
        }

        $hookResponse = [
            'account_id'   => $accountId,
            'account_name' => $accountName,
            'post_url'     => $logData['details']['post_url'],
            'status'       => $logData['status'],
        ];

        $this->threadsData['platform'] = 'threads';
        $this->threadsData['response'] = $hookResponse;

        if (!(\array_key_exists('keepLogs', $data) && $data['keepLogs'] === false)) {
            $this->logCreate($logData);
        }

        Hooks::doAction(Config::withPrefix('threads_post_publish'), $this->normalizePostData($postData), $hookResponse);

        return $this->threadsData;
    }

    public function postData($scheduleType, $post, $template)
    {
        $postData = [];
        $template = (object) $template;

        if ($scheduleType === Schedule::scheduleType['DIRECT_SHARE']) {
            $templateMedia = array_map(function ($item) {
                return $item['url'];
            }, $template->media);

            $postData['content'] = $template->content ?? null;
            $postData['media'] = $template->isAllImages ? $templateMedia : null;
            $postData['link'] = $template->isLinkCard ? $template->link : null;
            $postData['topic'] = $template->topic ?? null;
            $postData['comment'] = $template->comment ?? null;
        } else {
            $template->platform = 'threads';
            $postData = $this->replacePostContent($post['ID'], $template);

            if (!empty($template->topic)) {
                $postData['topic'] = $this->replaceTagValue($template->topic, (object) $post);
            }
        }

        return $postData;
    }

    private function shortCode($creationId)
    {
        $postPublishUrl = "{$this->baseUrl}/{$creationId}?";
        $params['fields'] = 'shortcode';
        $params['access_token'] = $this->accessToken;

        $publishPostUrlWithParams = $postPublishUrl . http_build_query($params);

        return $this->httpClient->request($publishPostUrlWithParams, 'GET', []);
    }

    private function createPost($postData)
    {
        $creationResponse = $this->creationId($postData);

        if (isset($creationResponse->error)) {
            return $creationResponse->error;
        }

        if (!$this->waitUntilMediaContainerReady($creationResponse->id)) {
            return (object) [
                'message' => 'Media container is not ready to publish yet.',
                'type'    => 'MediaNotReadyException',
                'code'    => 24,
            ];
        }

        $post = $this->threadsPublishPost($creationResponse->id);

        if (isset($post->error)) {
            return $post->error;
        }

        if (!empty($postData['comment'])) {
            $commentResponse = $this->createReplies($post->id, $postData['comment']);

            if (isset($commentResponse->error)) {
                $this->commentError[] = $commentResponse->error;
            }

            if (isset($commentResponse->id)) {
                $this->threadsPublishPost($commentResponse->id);
            }
        }

        return $this->shortCode($post->id);
    }

    private function carouselItemIds($postData)
    {
        $mediaIds = [];
        foreach ($postData['media'] as $mediaUrl) {
            $mediaId = $this->createCarouselContainer($mediaUrl);
            if ($mediaId) {
                $mediaIds[] = $mediaId;
            }
        }

        return $mediaIds;
    }

    private function createCarouselContainer($mediaUrl)
    {
        $containerPostUrl = "{$this->baseUrl}{$this->accountId}/threads?";

        $params = [
            'is_carousel_item' => true,
            'media_type'       => 'IMAGE',
            'image_url'        => $mediaUrl,
            'access_token'     => $this->accessToken];

        $postUrlWithParams = $containerPostUrl . http_build_query($params);
        $response = $this->httpClient->request($postUrlWithParams, 'POST', []);
        if (isset($response->error)) {
            $this->mediaUploadErrors[] = $response->error;

            return;
        }

        return $response->id;
    }

    private function creationId($postData)
    {
        $postUrl = "{$this->baseUrl}{$this->accountId}/threads?";
        $params = [];

        if (!empty($postData['content']) && empty($postData['media'])) {
            $params['media_type'] = 'TEXT';
        }

        if (!empty($postData['media']) && \count($postData['media']) === 1) {
            $params['media_type'] = 'IMAGE';
            $params['image_url'] = $postData['media'][0];
        }

        if (!empty($postData['media']) && \count($postData['media']) > 1) {
            $params['media_type'] = 'CAROUSEL';
            $carouselItemIds = $this->carouselItemIds($postData);
            $params['children'] = implode(',', $carouselItemIds);
        }

        if (!empty($postData['content'])) {
            $params['text'] = $postData['content'];
        }

        if (!empty($postData['topic'])) {
            $params['topic_tag'] = \strlen($postData['topic']) > self::TOPIC_CHARACTER_LIMIT
                ? substr($postData['topic'], 0, self::TOPIC_CHARACTER_LIMIT)
                : $postData['topic'];
        }

        if (!empty($postData['link'])) {
            $params['link_attachment'] = $postData['link'];
        }

        $params['access_token'] = $this->accessToken;

        $postUrlWithParams = $postUrl . http_build_query($params);

        return $this->httpClient->request($postUrlWithParams, 'POST', []);
    }

    private function threadsPublishPost($id)
    {
        $publishUrl = "{$this->baseUrl}{$this->accountId}/threads_publish?";

        $publishParams = [
            'creation_id'  => $id,
            'access_token' => $this->accessToken,
        ];

        $publishPostUrlWithParams = $publishUrl . http_build_query($publishParams);

        return $this->httpClient->request($publishPostUrlWithParams, 'POST', []);
    }

    private function createReplies($threadsId, $replyText)
    {
        $replyUrl = "{$this->baseUrl}me/threads?";

        $params = [
            'media_type'   => 'TEXT',
            'text'         => $replyText,
            'reply_to_id'  => $threadsId,
            'access_token' => $this->accessToken,
        ];

        $replyUrlWithParams = $replyUrl . http_build_query($params);

        return $this->httpClient->request($replyUrlWithParams, 'POST', []);
    }

    private function waitUntilMediaContainerReady($containerId)
    {
        if (empty($containerId)) {
            return false;
        }

        $statusUrl = "{$this->baseUrl}{$containerId}?";
        $params = [
            'fields'       => 'status,error_message',
            'access_token' => $this->accessToken,
        ];
        $statusUrlWithParams = $statusUrl . http_build_query($params);

        for ($retry = 0; $retry < self::MEDIA_READY_MAX_RETRIES; $retry++) {
            $statusResponse = $this->httpClient->request($statusUrlWithParams, 'GET', []);

            if (isset($statusResponse->error)) {
                $this->mediaUploadErrors[] = $statusResponse->error;

                return false;
            }

            $status = $statusResponse->status ?? null;
            if (\in_array($status, ['FINISHED', 'PUBLISHED'], true)) {
                return true;
            }

            if (\in_array($status, ['ERROR', 'EXPIRED'], true)) {
                $this->mediaUploadErrors[] = (object) [
                    'message' => $statusResponse->error_message ?? 'Media container is not publishable.',
                    'type'    => 'MediaContainerStatusException',
                    'code'    => 24,
                ];

                return false;
            }

            if ($status !== 'IN_PROGRESS') {
                return false;
            }

            sleep(self::MEDIA_READY_RETRY_SLEEP_SECONDS);
        }

        return false;
    }
}
