<?php

namespace Smartling\Helpers;

use Smartling\Base\ExportedAPI;
use Smartling\Bootstrap;
use Smartling\DbAl\LocalizationPluginProxyInterface;
use Smartling\Exception\BlogNotFoundException;
use Smartling\Exception\SmartlingConfigException;
use Smartling\Exception\SmartlingDataReadException;
use Smartling\Exception\SmartlingDirectRunRuntimeException;
use Smartling\Exception\SmartlingInvalidFactoryArgumentException;
use Smartling\Jobs\JobEntityWithBatchUid;
use Smartling\Submissions\SubmissionEntity;
use Smartling\Submissions\SubmissionManager;
use UnexpectedValueException;

class TranslationHelper
{
    use LoggerSafeTrait;

    public function __construct(
        private FileUriHelper $fileUriHelper,
        private LocalizationPluginProxyInterface $multilangProxy,
        private SiteHelper $siteHelper,
        private SubmissionManager $submissionManager,
    ) {
    }

    /**
     * @throws UnexpectedValueException
     */
    private function validateBlogs(int $sourceBlogId, int $targetBlogId): void
    {
        $blogs = $this->siteHelper->listBlogIdsFlat();

        if (!in_array($sourceBlogId, $blogs, true)) {
            $exception = new UnexpectedValueException(
                vsprintf('Unexpected value: sourceBlogId must be one of [%s], %s got',
                    [implode(', ',$blogs), $sourceBlogId])
            );

            $this->getLogger()->warning(
                vsprintf(
                    'Trying to get/create submission with invalid sourceBlogId, trace:\n\n%s\nRequest dump:\n%s',
                    [
                        $exception->getTraceAsString(),
                        base64_encode(serialize(Bootstrap::getRequestContext())),
                    ]
                )
            );
            throw $exception;
        }

        if (!in_array($targetBlogId, $blogs, true)) {
            $exception = new UnexpectedValueException(
                vsprintf('Unexpected value: targetBlogId must be one of [%s], %s got',
                    [implode(', ', $blogs), $targetBlogId])
            );

            $this->getLogger()->warning(
                vsprintf(
                    'Trying to get/create submission with invalid targetBlogId, trace:\n\n%s\nRequest dump:\n%s',
                    [
                        $exception->getTraceAsString(),
                        base64_encode(serialize(Bootstrap::getRequestContext())),
                    ]
                )
            );
            throw $exception;
        }

        if ($sourceBlogId === $targetBlogId) {
            $exception = new UnexpectedValueException('Unexpected value: sourceBlogId cannot be same as targetBlogId');

            $this->getLogger()->warning(
                vsprintf(
                    'Trying to get/create submission with same sourceBlogId and targetBlogId, trace:\n\n%s\nRequest dump:\n%s',
                    [
                        $exception->getTraceAsString(),
                        base64_encode(serialize(Bootstrap::getRequestContext())),
                    ]
                )
            );
            throw $exception;
        }
    }

    public function prepareSubmissionEntity(string $contentType, int $sourceBlog, int $sourceEntity, int $targetBlog, ?int $targetEntity = null): SubmissionEntity
    {
        $this->validateBlogs($sourceBlog, $targetBlog);

        return $this->submissionManager->getSubmissionEntity(
            $contentType,
            $sourceBlog,
            $sourceEntity,
            $targetBlog,
            $this->multilangProxy,
            $targetEntity
        );
    }

    /**
     * @throws BlogNotFoundException
     * @throws SmartlingConfigException
     * @throws SmartlingDirectRunRuntimeException
     * @throws SmartlingDataReadException
     * @throws SmartlingInvalidFactoryArgumentException
     */
    public function prepareSubmission(string $contentType, int $sourceBlog, int $sourceId, int $targetBlog, bool $clone = false): SubmissionEntity
    {
        if (0 === $sourceId) {
            throw new \InvalidArgumentException('Source id cannot be 0.');
        }
        $submission = $this->prepareSubmissionEntity(
            $contentType,
            $sourceBlog,
            $sourceId,
            $targetBlog
        );
        if ($submission->getFileUri() === '') {
            $submission->setFileUri($this->fileUriHelper->generateFileUri($submission));
        }

        if (0 === (int)$submission->getId()) {
            if (true === $clone) {
                $submission->setIsCloned(1);
            }
            $submission = $this->submissionManager->storeEntity($submission);
        }

        return $this->reloadSubmission($submission);
    }

    /**
     * @throws SmartlingDataReadException
     */
    public function reloadSubmission(SubmissionEntity $submission): SubmissionEntity
    {
        $result = $this->submissionManager->getEntityById($submission->getId());
        if ($result === null) {
            throw new SmartlingDataReadException(sprintf(
                'Error while reloading submission. Nothing returned from database. Original Submission: %s',
                var_export($submission->toArray(false), true),
            ));
        }
        return $result;
    }

    public function isRelatedSubmissionCreationNeeded(string $contentType, int $sourceBlogId, int $contentId, int $targetBlogId, bool $cloning = false): bool {
        if ($cloning && $this->submissionManager->submissionExists($contentType, $sourceBlogId, $contentId, $targetBlogId)) {
            return false;
        }

        return !$this->submissionManager->submissionExistsNoLastError($contentType, $sourceBlogId, $contentId, $targetBlogId);
    }

    /**
     * @throws SmartlingDataReadException
     */
    public function getExistingSubmissionOrCreateNew(string $contentType, int $sourceBlogId, int $contentId, int $targetBlogId, JobEntityWithBatchUid $jobInfo): SubmissionEntity {
        $submission = $this->submissionManager->getSubmissionEntity($contentType, $sourceBlogId, $contentId, $targetBlogId, $this->multilangProxy);
        if ($submission->getTargetId() === 0) {
            $this->getLogger()->debug("Got submission with 0 target id");
            $submission = $this->tryPrepareRelatedContent($contentType, $sourceBlogId, $contentId, $targetBlogId, $jobInfo);
        }
        return $submission;
    }

    /**
     * @throws BlogNotFoundException
     * @throws SmartlingConfigException
     * @throws SmartlingDirectRunRuntimeException
     * @throws SmartlingDataReadException
     * @throws SmartlingInvalidFactoryArgumentException
     */
    public function tryPrepareRelatedContent(string $contentType, int $sourceBlog, int $sourceId, int $targetBlog, JobEntityWithBatchUid $jobInfo, bool $clone = false): SubmissionEntity
    {
        $relatedSubmission = $this->prepareSubmission($contentType, $sourceBlog, $sourceId, $targetBlog, $clone);

        if (0 !== $sourceId && 0 === $relatedSubmission->getTargetId() &&
            SubmissionEntity::SUBMISSION_STATUS_FAILED !== $relatedSubmission->getStatus()
        ) {
            $message = vsprintf(
                'Trying to create corresponding translation placeholder for submission=%s for contentType=%s sourceBlog=%s sourceId=%s, targetBlog=%s',
                [
                    $relatedSubmission->getId(),
                    $contentType,
                    $sourceBlog,
                    $sourceId,
                    $targetBlog,
                ]
            );

            $relatedSubmission->setJobInfo($jobInfo->getJobInformationEntity());
            $serialized = $relatedSubmission->toArray(false);
            if (null === $serialized[SubmissionEntity::FIELD_FILE_URI]) {
                $relatedSubmission->setFileUri($this->fileUriHelper->generateFileUri($relatedSubmission));
            }
            $relatedSubmission = $this->submissionManager->storeEntity($relatedSubmission);
            // try to create target entity
            $relatedSubmission = apply_filters(ExportedAPI::FILTER_SMARTLING_PREPARE_TARGET_CONTENT, $relatedSubmission);
            if (!($relatedSubmission instanceof SubmissionEntity)) {
                $this->getLogger()->critical('Related submission not instance of ' . SubmissionEntity::class . ' after filter ' . ExportedAPI::FILTER_SMARTLING_PREPARE_TARGET_CONTENT . '. This is most likely due to an error outside of the plugins code.');
            }

            // add to upload queue
            $relatedSubmission->setStatus(SubmissionEntity::SUBMISSION_STATUS_NEW);
            $relatedSubmission = $this->submissionManager->storeEntity($relatedSubmission);
        } else {
            $message = vsprintf(
                'Skipping creation of translation placeholder for submission=%s, locale=%s.',
                [
                    $relatedSubmission->getId(),
                    $relatedSubmission->getTargetLocale(),
                ]
            );
        }

        $this->getLogger()->debug($message);

        return $this->reloadSubmission($relatedSubmission);
    }
}
