<?php

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

use AllowDynamicProperties;
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;
use finfo;

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

    public const POST_BASE_URL = 'https://bsky.social/xrpc/com.atproto.repo.createRecord';

    public const UPLOAD_BLOB_URL = 'https://bsky.social/xrpc/com.atproto.repo.uploadBlob';

    public const LINK_CARD_URL = 'https://cardyb.bsky.app/v1/extract';

    public const BYTE_LIMIT = 1000000; // 1MB

    private $httpClient;

    private $accessToken;

    private $mediaErrors = [];

    private $linkErrors = [];

    private $commentErrors = [];

    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 BlueskyRefreshTokenService($accountDetails);

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

        $postData = $this->postData($scheduleType, $post, $template);

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

        if (\array_key_exists('keepLogs', $data) && !$data['keepLogs']) {
            return;
        }

        $status = \is_object($postPublishResponse) && property_exists($postPublishResponse, 'uri') ? 1 : 0;
        $postUrl = isset($postPublishResponse->uri) ? $this->blueskyGetPostUrl($postPublishResponse->uri, $accountId) : null;

        $responseData = [
            'schedule_id' => $schedule_id,
            'details'     => [
                'account_id'   => $accountId,
                'account_name' => $accountName,
                'post_id'      => $post['ID'] ?? null,
                'response'     => $postPublishResponse,
                'post_url'     => $postUrl,
            ],
            'platform' => 'bluesky',
            'status'   => $status
        ];

        if (\count($this->mediaErrors)) {
            $responseData['details']['mediaErrors'] = $this->mediaErrors;
        }

        if (\count($this->linkErrors)) {
            $responseData['details']['linkErrors'] = $this->linkErrors;
        }

        if (\count($this->commentErrors)) {
            $responseData['details']['commentErrors'] = $this->commentErrors;
        }

        $this->logCreate($responseData);
    }

    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['createdAt'] = date('c');
            $postData['actor'] = $this->actor;
            $postData['comment'] = $template->comment ?? null;
            $postData['media'] = $template->isAllImages ? $templateMedia : null;
            $postData['link'] = $template->isLinkCard ? $template->link : null;
        } else {
            $template->platform = 'bluesky';
            $postData = $this->replacePostContent($post['ID'], $template);

            if (!empty($postData['featureImage'])) {
                $postData['media'] = [$postData['featureImage']];
            }
            if (!empty($postData['allImages'])) {
                $postData['media'] = $postData['allImages'];
            }
        }

        return $postData;
    }

    public function postMedia($filePath)
    {
        $fileContent = file_get_contents($filePath);
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->buffer($fileContent);
        $byteSize = \strlen($fileContent);
        $fileName = basename($filePath);

        if (empty($fileContent)) {
            $this->mediaErrors[] = [
                'error' => 'File content is empty',
                'file'  => $filePath,
            ];

            return [
                'status'  => 'error',
                'message' => 'File content is empty',
            ];
        }

        if ($byteSize > self::BYTE_LIMIT) {
            $this->mediaErrors[] = [
                'error' => 'File size exceeds the limit of 1MB',
                'file'  => $filePath,
            ];

            return [
                'status'  => 'error',
                'message' => 'File size exceeds the limit of 1MB',
            ];
        }

        return $this->uploadBlob($filePath, $fileName, $fileContent, $mimeType);
    }

    public function uploadBlob($filePath, $fileName, $fileContent, $mimeType)
    {
        $headers = [
            'Authorization' => 'Bearer ' . $this->accessToken,
            'Content-Type'  => $mimeType,

        ];

        $response = $this->httpClient->request(self::UPLOAD_BLOB_URL, 'POST', $fileContent, $headers);

        if (isset($response->blob)) {
            return [
                'status' => 'success',
                'blob'   => [
                    'alt'   => $fileName ?? '',
                    'image' => $response->blob,
                ]
            ];
        }

        $this->mediaErrors[] = [
            'error' => $response->message,
            'file'  => $filePath,
        ];

        return [
            'status'  => 'error',
            'message' => $response->message,
        ];
    }

    public function getBlobs($images)
    {
        if (\is_array($images) && \count($images) > 4) {
            $this->mediaErrors[] = [
                'error' => 'maximum 4 images are allowed',
                'file'  => $images,
            ];

            $images = \array_slice($images, 0, 4);
        }

        $blobs = [];
        foreach ($images as $image) {
            $response = $this->postMedia($image);
            if ($response['status'] === 'success') {
                $blobs[] = $response['blob'];
            }
        }

        if (!empty($blobs)) {
            return [
                'status' => 'success',
                'blobs'  => $blobs,
            ];
        }

        return [
            'status'  => 'error',
            'message' => 'No images found',
        ];
    }

    public function linkCardByUrl($url)
    {
        $params = [
            'url' => $url,
        ];

        $linkCardUrl = self::LINK_CARD_URL . '?' . http_build_query($params);

        $response = $this->httpClient->request($linkCardUrl, 'GET', [], []);

        if (isset($response->Error)) {
            return [
                'status'  => 'error',
                'message' => $response->Error,
            ];
        }

        if (isset($response->error) && !empty($response->error)) {
            return [
                'status'  => 'error',
                'message' => $response->error,
            ];
        }

        $linkData = [
            '$type'    => 'app.bsky.embed.external',
            'external' => [
                'uri'         => !empty($response->url) ? $response->url : $url,
                'title'       => $response->title ?? '',
                'description' => $response->description ?? '',
            ],
        ];

        if (!empty($response->image)) {
            $thump = $this->postMedia($response->image);

            if ($thump['status'] === 'success') {
                $blob = $thump['blob'];
                $linkData['external']['thumb'] = $blob['image'];
            }
        }

        return [
            'status' => 'success',
            'data'   => $linkData
        ];
    }

    public function buildFacetsFromText(string $text): array
    {
        $facets = [];

        // Match either a hashtag or a link
        preg_match_all('/(#\w+)|(https?:\/\/[^\s]+)/u', $text, $matches, PREG_OFFSET_CAPTURE);

        foreach ($matches[0] as $match) {
            [$matchText, $charPos] = $match;

            // Get byte offset for UTF-8
            $before = mb_substr($text, 0, $charPos, 'UTF-8');
            $byteStart = \strlen(mb_convert_encoding($before, 'UTF-8'));
            $byteEnd = $byteStart + \strlen(mb_convert_encoding($matchText, 'UTF-8'));

            if (str_starts_with($matchText, '#')) {
                // Hashtag
                $facets[] = [
                    'index' => [
                        'byteStart' => $byteStart,
                        'byteEnd'   => $byteEnd,
                    ],
                    'features' => [
                        [
                            '$type' => 'app.bsky.richtext.facet#tag',
                            'tag'   => substr($matchText, 1), // Remove '#'
                        ]
                    ]
                ];
            } elseif (str_starts_with($matchText, 'http')) {
                // Link
                $facets[] = [
                    'index' => [
                        'byteStart' => $byteStart,
                        'byteEnd'   => $byteEnd,
                    ],
                    'features' => [
                        [
                            '$type' => 'app.bsky.richtext.facet#link',
                            'uri'   => $matchText,
                        ]
                    ]
                ];
            }
        }

        return $facets;
    }

    private function createPost($postData)
    {
        $text = $postData['content'];
        $facets = $this->buildFacetsFromText($text);
        $recordData = [
            'text'      => $text,
            'createdAt' => date('c'),
            'facets'    => $facets,
            'actor'     => $this->actor,

        ];

        if (isset($postData['media']) && !empty($postData['media'])) {
            $response = $this->getBlobs($postData['media']);

            if ($response['status'] === 'success') {
                $recordData['embed'] = [
                    '$type'  => 'app.bsky.embed.images',
                    'images' => $response['blobs'],
                ];
            }
        }

        if (isset($postData['link']) && !empty($postData['link'])) {
            $response = $this->linkCardByUrl($postData['link']);

            if ($response['status'] === 'success') {
                $recordData['embed'] = $response['data'];
            }

            if ($response['status'] === 'error') {
                $this->linkErrors[] = [
                    'error' => $response['message'],
                    'link'  => $postData['link'],
                ];
            }
        }

        $body = [
            'repo'       => $this->actor,
            'collection' => 'app.bsky.feed.post',
            'record'     => $recordData,
        ];

        $headers = [
            'Authorization' => 'Bearer ' . $this->accessToken,
            'Content-Type'  => 'application/json',
        ];

        $postResponse = $this->httpClient->request(self::POST_BASE_URL, 'POST', json_encode($body), $headers);

        if (isset($postResponse->uri, $postData['comment']) && !empty($postData['comment'])) {
            $this->createComment($postData['comment'], $postResponse);
        }

        return $postResponse;
    }

    private function blueskyGetPostUrl($uri, $handle)
    {
        $parts = explode('/', $uri); // ['at:', '', 'did:plc:...', 'app.bsky.feed.post', 'rkey']
        $rkey = end($parts); // the post ID

        return "https://bsky.app/profile/{$handle}/post/{$rkey}";
    }

    private function createComment($text, $postResponse)
    {
        $postDetails = [
            'uri' => $postResponse->uri,
            'cid' => $postResponse->cid,
        ];

        $recordData = [
            'text'      => $text,
            'createdAt' => date('c'),
            'reply'     => [
                'root'   => $postDetails,
                'parent' => $postDetails,
            ]

        ];

        $body = [
            'repo'       => $this->actor,
            'collection' => 'app.bsky.feed.post',
            'record'     => $recordData,
        ];

        $headers = [
            'Authorization' => 'Bearer ' . $this->accessToken,
            'Content-Type'  => 'application/json',
        ];

        $response = $this->httpClient->request(self::POST_BASE_URL, 'POST', json_encode($body), $headers);

        if (isset($response->uri)) {
            return $response;
        }

        $this->commentErrors[] = [
            'error' => $response->message,
            'post'  => $postResponse->uri,
        ];
    }
}
