<?php

namespace RtmBusiness\PostSync\Action;

use RtmBusiness\PostSync\Logger;
use RtmBusiness\PostSync\Model\SyncData;
use RtmBusiness\PostSync\Model\SyncableChild;
use RtmBusiness\PostSync\SyncController;
use WP_Error;

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

    /**
     * @inheritDoc
     */
    public function execute(int $parentPostId)
    {
        if (!$this->validate()) {
            return;
        }
        // Check if the checkboxes were submitted
        if (isset($_POST['children'])) {
            // Sanitize and save the values of the checkboxes
            $selectedChildBlogIds = array_map('absint', $_POST['children']);
            $options['overwriteSlugs'] = explode(",", $_POST['overwriteDuplicateSlugs']);

            $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::instance()->getSyncDataWithChild('post', get_current_blog_id(), $parentPostId);
            //If the current post being editted already has a parent relation, save the new sync relation inside this same relation
            if ($parentRelation) {
                $blogIds = array_map(function ($item) {
                    return $item->blogId;
                }, $parentRelation->getLinkedChildren());

                $selectedChildBlogIds = array_merge($selectedChildBlogIds, $blogIds);
                $syncData = $parentRelation;
            } else { //Otherwise get or create new relation
                $syncData = SyncController::instance()->getSyncData('post', get_current_blog_id(), $parentPostId);
            }

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

            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 ($_POST['action'] === 'editpost') {
            // If no checkboxes were submitted, delete the meta value
            SyncController::instance()->deleteSyncData(get_current_blog_id(), $parentPostId);
            Logger::info("Sync relation deleted", "All sync relationships have been deleted", "PublishAction", "sync", ['fromBlog' => get_current_blog_id(), 'fromPost' => $parentPostId]);
        }
    }


    /**
     * 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);

        $sourcePost = get_post($sourcePostId);

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

        $sourcePostData = (array)$sourcePost;

        // 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');
        }

        $sourceSlug = $sourcePostData['post_name'];

        $sourceMetaData = get_post_custom($sourcePostId);

        $sourceThumbnailId = get_post_thumbnail_id($sourcePostId);
        $sourceThumbnailMeta = wp_get_attachment_metadata($sourceThumbnailId);
        $sourceThumbnailFile = get_attached_file($sourceThumbnailId);
        if ($sourceThumbnailFile) {
            $sourceThumbnailHash = md5_file($sourceThumbnailFile);
        }

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

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

        restore_current_blog();

        switch_to_blog($targetBlogId);

        $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) {
                        return new WP_Error('duplicate_slug', 'Could not find a unique slug after 10 tries.');
                    }

                } 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
                    $sourcePostData['post_name'] = $alt_slug;
                }
            }
        }

        // 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;
            }
        }

        $sync_metadata = get_site_option('postsync_sync_metadata', false) == 1;

        if ($sync_metadata) {
            $excluded_meta_fields = get_site_option("postsync_" . $sourcePost->post_type . "_excluded_meta_fields", []);
            // Allow other plugins to modify the excluded meta fields
            $excluded_meta_fields = apply_filters('postsync_excluded_meta_fields', $excluded_meta_fields, $sourcePost->post_type);

            //Define some default excluded fields
            $excluded_meta_fields[] = '_wp_old_slug';
            $excluded_meta_fields[] = '_thumbnail_id';

            //Syncing the metadata
            foreach ($sourceMetaData as $meta_key => $meta_values) {
                if (in_array($meta_key, $excluded_meta_fields)) {
                    continue;
                }
                foreach ($meta_values as $meta_value) {
                    update_post_meta($inserted_post_id, $meta_key, $meta_value);
                }
            }
        }

        if ($sync_taxonomies) {
            foreach ($taxonomies_terms as $taxonomy => $terms) {
                // Set the terms on the target post for this taxonomy
                wp_set_object_terms($inserted_post_id, $terms, $taxonomy);
            }
        }

        // Syncing the featured image
        if ($sourceThumbnailId) {
            $targetThumbnailHash = get_post_meta($inserted_post_id, '_thumbnail_hash', true);

            // Check if the image has never been synced or if it has been updated
            if (!$targetThumbnailHash || $targetThumbnailHash !== $sourceThumbnailHash) {
                $targetThumbnailId = $this->insertAttachmentFromPath($sourceThumbnailFile, $inserted_post_id);

                if (!is_wp_error($targetThumbnailId)) {
                    // Set the featured image on the target post
                    set_post_thumbnail($inserted_post_id, $targetThumbnailId);

                    // Update the attachment metadata
                    wp_update_attachment_metadata($targetThumbnailId, $sourceThumbnailMeta);

                    // Store the new hash in the target postmeta
                    update_post_meta($inserted_post_id, '_thumbnail_hash', $sourceThumbnailHash);
                    update_post_meta($inserted_post_id, '_thumbnail_id', $targetThumbnailId);
                } else {
                    Logger::error("Image Upload Failed", "Uploading the featured image to child blog has failed", "PublishAction", 'sync', [
                        'uploadingTo' => get_current_blog_id(),
                        'error' => $targetThumbnailId->get_error_messages(),
                        'stackTrace' => $targetThumbnailId->get_error_data()
                    ]);
                }
            }
        }

        restore_current_blog();
        return $inserted_post_id;
    }


    /**
     * 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
     * @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): bool|WP_Error
    {
        $children = [];
        foreach ($selectedChildBlogIds as $childBlogId) {
            $newPostId = $this->syncToChild(in_array($childBlogId, $options['overwriteSlugs']), $syncData->parentBlogId, $syncData->parentPostId, $childBlogId, $syncData->getLinkedChildPostId($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
        if ($syncData->getLinkedChildren() !== $children) {
            $syncData->setLinkedChildren($children);
            SyncController::instance()->insertOrUpdateSyncData($syncData);
        }
        return true;
    }

    /**
     * Insert an attachment from a file path.
     *
     * @param string $path The path of the file to be uploaded.
     * @param int $post_id (Optional) The ID of the post to which the attachment should be attached. Default is 0.
     * @return int|WP_Error The ID of the inserted attachment on success, or WP_Error object on failure.
     */
    public function insertAttachmentFromPath(string $path, int $post_id = 0): WP_Error|int
    {
        $filename = basename($path);
        $upload_file = wp_upload_bits($filename, null, file_get_contents($path));
        if (!$upload_file['error']) {
            $wp_filetype = wp_check_filetype($filename);
            $attachment = array(
                'post_mime_type' => $wp_filetype['type'],
                'post_parent' => $post_id,
                'post_title' => preg_replace('/\.[^.]+$/', '', $filename),
                'post_content' => '',
                'post_status' => 'inherit'
            );
            $attachment_id = wp_insert_attachment($attachment, $upload_file['file'], $post_id);
            if (!is_wp_error($attachment_id)) {
                require_once(ABSPATH . "wp-admin" . '/includes/image.php');
                $attachment_data = wp_generate_attachment_metadata($attachment_id, $upload_file['file']);
                wp_update_attachment_metadata($attachment_id, $attachment_data);
            }
            return $attachment_id;
        } else {
            return new WP_Error('upload_error', $upload_file['error']);
        }
    }
}
