<?php

namespace RtmBusiness\PostSync\Action;

use RtmBusiness\PostSync\Logger;
use RtmBusiness\PostSync\SyncHandlers\DefaultSyncHandler;
use RtmBusiness\PostSync\Model\SyncData;
use RtmBusiness\PostSync\Model\SyncableChild;
use RtmBusiness\PostSync\SyncController;
use RtmBusiness\PostSync\SyncHandlers\ImageSyncHandler;
use WP_Error;

/**
 * This action is triggered on publishing or updating of a post
 */
class PublishAction implements SyncAction
{

    public function __construct()
    {
        add_action('postsync_before_sync', [$this, 'prepareSourcePostData'], 10, 4);

        add_action('postsync_after_insert_post', [new DefaultSyncHandler(), 'handle'], 20, 4);
        add_action('postsync_after_insert_post', [new ImageSyncHandler(), 'handle'], 10, 4);
    }


    /**
     * Prepares the paramaters to be used in the syncing process and stores them in the global, this is always executed in sourceBlogId context
     * @param $sourceBlogId
     * @param $sourcePostId
     * @param $targetBlogId
     * @param $targetPostId
     * @return void
     */
    public function prepareSourcePostData($sourceBlogId, $sourcePostId, $targetBlogId, $targetPostId)
    {
        global $rtm_source_postdata;

        $sourcePost = get_post($sourcePostId);
        $sourcePostData = (array)$sourcePost;
        $sourceSlug = $sourcePostData['post_name'];
        $sourceMetaData = get_post_custom($sourcePostId);

        $sourceAttachments = get_posts([
            'post_type' => 'attachment',
            'posts_per_page' => -1,
            'post_parent' => $sourcePostId,
        ]);

        $formattedSourceAttachments = [];

        foreach ($sourceAttachments as $key => $attachment) {
            $formattedSourceAttachments[$key] = [
                'attachment' => $attachment,
                'filename' => get_attached_file($attachment->ID),
                'url' => wp_get_attachment_url($attachment->ID),
                'file_path' => get_post_meta(
                    $attachment->ID,
                    '_wp_attached_file',
                    true
                ),
                'meta' => get_post_meta($attachment->ID, '_wp_attachment_metadata', true)
            ];
        }

        $sourceThumbnailId = get_post_thumbnail_id($sourcePostId);
        $sourceThumbnailMeta = wp_get_attachment_metadata($sourceThumbnailId);

        // If a post thumbnail is set, add it to the array of attachments
        if ($sourceThumbnailId) {
            $thumbnailAttachment = get_post($sourceThumbnailId);

            $formattedSourceAttachments[] = [
                'attachment' => $thumbnailAttachment,
                'filename' => get_attached_file($thumbnailAttachment->ID),
                'url' => wp_get_attachment_url($thumbnailAttachment->ID),
                'file_path' => get_post_meta(
                    $thumbnailAttachment->ID,
                    '_wp_attached_file',
                    true
                ),
                'meta' => get_post_meta($thumbnailAttachment->ID, '_wp_attachment_metadata', true)
            ];
        }

        // Fetch woocommerce product gallery images
        if (class_exists('WooCommerce') && $sourcePost->post_type == 'product') {
            $product = wc_get_product($sourcePostId);
            if ($product) {
                $galleryImages = $rtm_source_postdata['postedGalleryImages'] ?? [];
                if ($galleryImages) {
                    foreach ($galleryImages as $galleryImageId) {
                        $galleryImage = get_post($galleryImageId);
                        $formattedSourceAttachments[] = [
                            'attachment' => $galleryImage,
                            'filename' => get_attached_file($galleryImage->ID),
                            'url' => wp_get_attachment_url($galleryImage->ID),
                            'file_path' => get_post_meta(
                                $galleryImage->ID,
                                '_wp_attached_file',
                                true
                            ),
                            'is_gallery' => true,
                            'meta' => get_post_meta($galleryImage->ID, '_wp_attachment_metadata', true)
                        ];
                    }
                }
            }
        }


        $syncTaxonomies = get_site_option('postsync_sync_taxonomies', false) == 1;
        $taxonomyTerms = [];
        if ($syncTaxonomies) {
            // Get the taxonomies for the source post
            $taxonomies = get_object_taxonomies($sourcePost);

            // Reduce the taxonomies to an associative array of terms
            $taxonomyTerms = array_reduce($taxonomies, function ($carry, $tax) use ($sourcePostId) {
                $carry[$tax] = wp_get_object_terms($sourcePostId, $tax, ['fields' => 'slugs']);
                return $carry;
            }, []);
        }


        // Save the data in the $GLOBALS array so that it can be accessed in the 'syncToChild' function
        $rtm_source_postdata = [
            'sourcePostData' => $sourcePostData,
            'formattedSourceAttachments' => $formattedSourceAttachments,
            'sourceSlug' => $sourceSlug,
            'sourceMetaData' => $sourceMetaData,
            'sourceThumbnailId' => $sourceThumbnailId,
            'sourceThumbnailMeta' => $sourceThumbnailMeta,
            'taxonomyTerms' => $taxonomyTerms,
            'galleryImagesMapping' => $galleryImagesMapping ?? [],
        ];
    }

    /**
     * @inheritDoc
     */
    public function execute(int $postId)
    {
        if (!$this->validate()) {
            return;
        }
        $syncController = SyncController::instance();
        //Handles syncing from child to parent and siblings & from quick edit
        if (isset($_POST['syncToParentAndSiblings']) || isset($_POST['action']) && $_POST['action'] == 'inline-save') {
            $syncData = $syncController->getSyncDataWithChildOrParent('post', get_current_blog_id(), $postId);
            if ($syncData !== null) {
                //Collect list of blogIds this child may sync to
                $syncToBlogIds = [];
                $slugOptions = [];
                $currentBlogId = get_current_blog_id();

                //Publish permissions for the current blog is required to sync to other blogs
                if (current_user_can_for_blog(get_current_blog_id(), "postsync_can_publish")) {
                    foreach ($syncData->getLinkedChildren() as $child) {
                        if ($child->blogId != $currentBlogId) {
                            if (current_user_can_for_blog($child->blogId, "postsync_can_publish")) {
                                $syncToBlogIds[] = $child->blogId;
                            }
                        }
                        if ($child->isOverwriteSlug()) {
                            $slugOptions[] = $child->blogId;
                        }
                    }
                    if (current_user_can_for_blog($syncData->parentBlogId, "postsync_can_publish") && $currentBlogId != $syncData->parentBlogId) {
                        $syncToBlogIds[] = $syncData->parentBlogId;
                    }
                }
                $options['overwriteSlugs'] = $slugOptions;
                $successfulSync = $this->syncToBlogs($syncData, $syncToBlogIds, $options, $currentBlogId, $postId);

                if (is_wp_error($successfulSync)) {
                    Logger::error(
                        "Failed Sync from Child",
                        "An error occurred while syncing from " . $_POST['action'] == 'inline-save' ? 'quick edit' : 'a child post',
                        "PublishAction",
                        "sync",
                        [
                            'fromBlog' => $currentBlogId,
                            'toBlogs' => $syncToBlogIds,
                            'error' => $successfulSync->get_error_messages(),
                            'stackTrace' => $successfulSync->get_error_data()
                        ]
                    );
                } else {
                    Logger::info("Successful Sync", "Post has been synced successfully from " . $_POST['action'] == 'inline-save' ? 'quick edit' : 'a child post', "PublishAction", "sync", ['fromBlog' => $currentBlogId, 'toBlogs' => $syncToBlogIds]);
                }
            }
        }
        // Check if the checkboxes were submitted
        if (isset($_POST['children'])) {
            global $rtm_source_postdata;
            // Sanitize and save the values of the checkboxes
            $selectedChildBlogIds = array_map('absint', $_POST['children']);
            $options['overwriteSlugs'] = explode(",", $_POST['overwriteDuplicateSlugs']);

            //Store selected product gallery images to be used in ImageSyncHandler
            if (!empty($_POST['product_image_gallery'])) {
                $image_gallery = explode(',', $_POST['product_image_gallery']);
                $rtm_source_postdata['postedGalleryImages'] = array_filter($image_gallery);
            }

            $currentBlogId = get_current_blog_id();
            if (!current_user_can("postsync_can_publish")) {
                foreach ($selectedChildBlogIds as $id) {
                    if (!current_user_can_for_blog($id, "postsync_can_publish")) {
                        Logger::warning("Publish", "User doesn't have permission to manage this sync relation", "PublishAction", "sync", ['fromBlog' => $currentBlogId, 'toBlogs' => $selectedChildBlogIds]);
                        return;
                    }
                }
                Logger::warning("Publish", "User doesn't have permission to manage this sync relation", "PublishAction", "sync", ['fromBlog' => $currentBlogId, 'toBlogs' => $selectedChildBlogIds]);
                return;
            }

            Logger::debug("Publish", "Attempting to publish post", "PublishAction", "sync", ['fromBlog' => $currentBlogId, 'toBlogs' => $selectedChildBlogIds]);

            $parentRelation = $syncController->getSyncDataWithChild('post', get_current_blog_id(), $postId);
            //If parentRelation exists, we are a child post and we have selected siblings to sync to
            if ($parentRelation) {
                $blogIds = array_map(function ($item) {
                    return $item->blogId;
                }, $parentRelation->getLinkedChildren());
                //Merge the selected siblings with the existing siblings
                $selectedChildBlogIds = array_merge($selectedChildBlogIds, $blogIds);
                $syncData = $parentRelation;
            } else { //Otherwise get or create new relation
                $syncData = $syncController->getSyncData('post', get_current_blog_id(), $postId);
            }

            $successfulSync = $this->syncToBlogs($syncData, $selectedChildBlogIds, $options, $syncData->parentBlogId, $syncData->parentPostId);

            if (is_wp_error($successfulSync)) {
                Logger::error(
                    "Failed Sync",
                    "An error occurred while syncing",
                    "PublishAction",
                    "sync",
                    [
                        'fromBlog' => $currentBlogId,
                        'toBlogs' => $selectedChildBlogIds,
                        'error' => $successfulSync->get_error_messages(),
                        'stackTrace' => $successfulSync->get_error_data()
                    ]
                );
            } else {
                Logger::info("Successful Sync", "Post has been synced successfully", "PublishAction", "sync", ['fromBlog' => $currentBlogId, 'toBlogs' => $selectedChildBlogIds]);
            }
        } else if (isset($_POST['action']) && $_POST['action'] === 'editpost') {
            //Parent is updating post with no checkboxes selected, delete sync relation
            if ($syncController->parentOrChild(get_current_blog_id(), $postId) === 'parent') {
                $syncController->deleteSyncData(get_current_blog_id(), $postId);
                Logger::info("Sync relation deleted", "All sync relationships have been deleted", "PublishAction", "sync", ['fromBlog' => get_current_blog_id(), 'fromPost' => $postId]);
            }
        }
    }


    /**
     * Inserts or updates a post from one blog to another
     *
     * @param bool $overWriteSlugs Whether slugs should be overwritten
     * @param int $sourceBlogId The blog id of the post to be cloned
     * @param $sourcePostId int The post id of the post to be cloned to child sites
     * @param $targetBlogId int The blog id of the target blog for the sync
     * @param int|null $targetPostId int The post id on the target blog that should be updated or null if this is a new insert
     * @return int|WP_Error Returns the post id of the inserted or updated post or an error
     */
    public function syncToChild(bool $overWriteSlugs, int $sourceBlogId, int $sourcePostId, int $targetBlogId, int $targetPostId = null): int|WP_Error
    {

        switch_to_blog($sourceBlogId);


        // prevent "Fatal error: Maximum function nesting level reached"
        remove_action('save_post', __FUNCTION__);

        $sourcePost = get_post($sourcePostId);


        // If the source post is transitioning to 'draft', do nothing.
        if ('draft' === $sourcePost->post_status) {
            return new WP_Error('draft_status', 'Cannot sync draft posts');
        }

        do_action('postsync_before_sync', $sourceBlogId, $sourcePostId, $targetBlogId, $targetPostId);

        //Retrieve the fetched post data from the global variable populated by the 'postsync_before_sync' action
        global $rtm_source_postdata;
        $sourcePostData = $rtm_source_postdata['sourcePostData'];

        restore_current_blog();

        switch_to_blog($targetBlogId);

        $this->handleDuplicateSlugs($overWriteSlugs, $targetPostId);

        // if post_id is provided, update existing post instead of creating a new one
        if ($targetPostId) {
            $sourcePostData['ID'] = $targetPostId;
            $updated_post_id = wp_update_post($sourcePostData);
            if (is_wp_error($updated_post_id)) {
                $trace = wp_debug_backtrace_summary();
                $updated_post_id->add_data(['trace' => $trace]);
                return $updated_post_id;
            }

            $inserted_post_id = $updated_post_id;
        } else {
            $sourcePostData['ID'] = null;
            $inserted_post_id = wp_insert_post($sourcePostData);
            if (is_wp_error($inserted_post_id)) {
                $trace = wp_debug_backtrace_summary();
                $inserted_post_id->add_data(['trace' => $trace]);
                return $inserted_post_id;
            }
        }

        do_action('postsync_after_insert_post', $sourceBlogId, $sourcePostId, $targetBlogId, $inserted_post_id);

        $this->syncTaxonomies($inserted_post_id);

        restore_current_blog();
        return $inserted_post_id;
    }


    /**
     * Syncs the cached taxonomies to the target post
     * @param int $targetPostId The post id of the target post
     * @return void
     */
    private function syncTaxonomies(int $targetPostId)
    {
        global $rtm_source_postdata;
        $taxonomyTerms = $rtm_source_postdata['taxonomyTerms'];
        $syncTaxonomies = get_site_option('postsync_sync_taxonomies', false) == 1;
        if ($syncTaxonomies) {
            foreach ($taxonomyTerms as $taxonomy => $terms) {
                // Set the terms on the target post for this taxonomy
                wp_set_object_terms($targetPostId, $terms, $taxonomy);
            }
        }
    }


    /**
     * Validates whether the conditions are met to execute this action
     *
     * @return bool Whether the action may be executed
     */
    private function validate(): bool
    {
        if (did_action('rps_publish_post') >= 2) {
            return false;
        }

        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return false;
        }
        return true;
    }

    /**
     * Syncs the selected child blogs with the parent specified in SyncData
     *
     * @param SyncData $syncData
     * @param array $selectedChildBlogIds
     * @param array $options The sync options
     * @param int $sourceBlogId
     * @param int $sourcePostId
     * @return bool|WP_Error Returns true if sync was successful or an error if syncing has failed for some reason
     */
    private function syncToBlogs(SyncData $syncData, array $selectedChildBlogIds, array $options, int $sourceBlogId, int $sourcePostId): bool|WP_Error
    {
        $children = [];
        foreach ($selectedChildBlogIds as $childBlogId) {
            $newPostId = $this->syncToChild(in_array($childBlogId, $options['overwriteSlugs']), $sourceBlogId, $sourcePostId, $childBlogId, $syncData->getLinkedPostId($childBlogId));
            if (!is_wp_error($newPostId)) {
                $newChild = new SyncableChild($childBlogId, $newPostId);
                $children[] = $newChild;

                if ($options['overwriteSlugs']) {
                    $newChild->setOverwriteSlug(in_array($childBlogId, $options['overwriteSlugs']));
                }
            } else {
                $trace = wp_debug_backtrace_summary();
                $newPostId->add_data(['trace' => $trace]);
                return $newPostId;
            }
        }
        //if any relationship between parent -> child is altered, update or insert in table; only if sync action is triggered from parent blog
        if ($sourceBlogId == $syncData->parentBlogId) {
            if ($syncData->getLinkedChildren() !== $children) {
                $syncData->setLinkedChildren($children);
                SyncController::instance()->insertOrUpdateSyncData($syncData);
            }
        }
        return true;
    }


    /**
     * Handles the scenario where the source post has the same slug as a post on the target blog
     * @param bool $overWriteSlugs true if slug on target blog gets suffix, false if slug on target blog gets overwritten
     * @param int|null $targetPostId
     * @return void
     */
    private function handleDuplicateSlugs(bool $overWriteSlugs, int $targetPostId = null): void
    {
        global $rtm_source_postdata;
        $sourceSlug = $rtm_source_postdata['sourceSlug'];

        //Check if there is a post with the same slug on the target blog
        $postWithSameSlug = get_posts([
            'name' => $sourceSlug,
            'post_type' => 'any',
            'post_status' => 'any',
            'numberposts' => 1
        ]);

        if ($postWithSameSlug) {
            //Verify that the post with the same slug is not the post we're currently syncing to
            if ($postWithSameSlug[0]->ID !== $targetPostId) {
                $suffix = 2;  // The suffix to append to the slug
                $tries = 0;   // The number of tries to find an alternative slug

                // If a post with the same slug already exists on the target blog, append a suffix to the slug
                do {
                    $alt_slug = $sourceSlug . '-' . $suffix;
                    $posts_with_same_slug = get_posts([
                        'name' => $alt_slug,
                        'post_type' => 'any',
                        'post_status' => 'any',
                        'numberposts' => 1
                    ]);
                    $suffix++;
                    $tries++;

                    // After 10 tries, if no unique slug is found, return a WordPress error
                    if ($tries >= 10 && $posts_with_same_slug) {
                        new WP_Error('duplicate_slug', 'Could not find a unique slug after 10 tries.');
                        return;
                    }
                } while ($posts_with_same_slug);

                if ($overWriteSlugs === true) {
                    //If overwrite is enabled, the duplicate slug on target blog gets a suffix
                    $postWithSameSlug[0]->post_name = $alt_slug;
                    wp_update_post($postWithSameSlug[0]);
                } else {
                    //If overwrite is disbaled, the newly inserted post gets a suffix
                    $rtm_source_postdata['post_name'] = $alt_slug;
                }
            }
        }
    }
}
