<?php

namespace FleekDash\Controllers\Schema;

use WP_Error;

if (!\function_exists('fleekdash_log_debug')) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/debug-helpers.php';
}

/**
 * REST CRUD Handler
 *
 * Handles REST API CRUD operations for custom post types
 *
 * @package FleekDash\Controllers\Schema
 */
class RESTCrudHandler
{
    private const MESSAGE_POST_NOT_FOUND = 'Post not found';

    /** @var list<string> */
    private const RESERVED_JSON_KEYS = ['title', 'content', 'status', 'excerpt', 'author', 'featured_media', 'meta'];

    private SchemaFormatter $formatter;

    public function __construct()
    {
        $this->formatter = new SchemaFormatter();
    }

    /**
     * Register REST API for a specific custom post type
     *
     * @param string $post_type_slug Post type slug
     * @param object $post_type_obj Post type object
     */
    public function registerCustomPostTypeRest($post_type_slug, $post_type_obj)
    {
        // Define the REST base
        $rest_base = !empty($post_type_obj->rest_base) ? $post_type_obj->rest_base : $post_type_slug;

        // Register collection route
        \register_rest_route('wp/v2', '/' . $rest_base, [
            [
                'methods' => 'GET',
                'callback' => function ($request) use ($post_type_slug) {
                    return $this->getCustomPostTypeItems($request, $post_type_slug);
                },
                'permission_callback' => function () {
                    return \current_user_can('edit_posts');
                }
            ],
            [
                'methods' => 'POST',
                'callback' => function ($request) use ($post_type_slug) {
                    return $this->createCustomPostTypeItem($request, $post_type_slug);
                },
                'permission_callback' => function () {
                    return \current_user_can('edit_posts');
                }
            ]
        ]);

        // Register individual item route
        \register_rest_route('wp/v2', '/' . $rest_base . '/(?P<id>\d+)', [
            [
                'methods' => 'GET',
                'callback' => function ($request) use ($post_type_slug) {
                    return $this->getCustomPostTypeItem($request, $post_type_slug);
                },
                'permission_callback' => function () {
                    return \current_user_can('edit_posts');
                }
            ],
            [
                'methods' => ['PUT', 'PATCH'],
                'callback' => function ($request) use ($post_type_slug) {
                    return $this->updateCustomPostTypeItem($request, $post_type_slug);
                },
                'permission_callback' => function () {
                    return \current_user_can('edit_posts');
                }
            ],
            [
                'methods' => 'DELETE',
                'callback' => function ($request) use ($post_type_slug) {
                    return $this->deleteCustomPostTypeItem($request, $post_type_slug);
                },
                'permission_callback' => function () {
                    return \current_user_can('edit_posts');
                }
            ]
        ]);
    }

    /**
     * Get multiple custom post type items
     *
     * @param \WP_REST_Request $request Request object
     * @param string $post_type_slug Post type
     * @return \WP_REST_Response Response
     */
    public function getCustomPostTypeItems($request, $post_type_slug)
    {
        $per_page = $request->get_param('per_page') ?: 10;
        $page = $request->get_param('page') ?: 1;

        $posts = \get_posts([
            'post_type' => $post_type_slug,
            'posts_per_page' => $per_page,
            'paged' => $page,
            'post_status' => 'any',
            'fields' => 'ids',
            'no_found_rows' => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'ignore_sticky_posts' => true,
        ]);

        $items = [];
        foreach ($posts as $post_id) {
            $post = \get_post($post_id);
            if ($post) {
                $items[] = $this->formatter->formatPostForRest($post);
            }
        }

        return \rest_ensure_response($items);
    }

    /**
     * Get single custom post type item
     *
     * @param \WP_REST_Request $request Request object
     * @param string $post_type_slug Post type
     * @return \WP_REST_Response|WP_Error Response or error
     */
    public function getCustomPostTypeItem($request, $post_type_slug)
    {
        $id = $request->get_param('id');
        $post = \get_post($id);

        $not_found = $this->postNotFoundIfMismatch($post, $post_type_slug);
        if ($not_found !== null) {
            return $not_found;
        }

        return \rest_ensure_response($this->formatter->formatPostForRest($post));
    }

    /**
     * Create new custom post type item
     *
     * @param \WP_REST_Request $request Request object
     * @param string $post_type_slug Post type
     * @return \WP_REST_Response|WP_Error Response or error
     */
    public function createCustomPostTypeItem($request, $post_type_slug)
    {
        $params = $request->get_json_params();
        if (!\is_array($params)) {
            $params = [];
        }

        $post_data = $this->buildInsertPostData($params, $post_type_slug);
        $post_id = \wp_insert_post($post_data);

        if (\is_wp_error($post_id)) {
            return $post_id;
        }

        $this->applyMetaBundleFromParams((int) $post_id, $params, $post_type_slug);
        $this->applyTaxonomiesOnCreate((int) $post_id, $post_type_slug, $params);
        $this->applyCategoriesAndTagsOnCreate((int) $post_id, $params);
        $this->applyFeaturedMediaOnCreate((int) $post_id, $params);

        $post = \get_post($post_id);
        fleekdash_log_debug("FleekDash: Created {$post_type_slug} post ID: {$post_id}");

        return \rest_ensure_response($this->formatter->formatPostForRest($post));
    }

    /**
     * Update custom post type item
     *
     * @param \WP_REST_Request $request Request object
     * @param string $post_type_slug Post type
     * @return \WP_REST_Response|WP_Error Response or error
     */
    public function updateCustomPostTypeItem($request, $post_type_slug)
    {
        $id = $request->get_param('id');
        $params = $request->get_json_params();
        if (!\is_array($params)) {
            $params = [];
        }

        fleekdash_log_debug("FleekDash: Updating {$post_type_slug} ID {$id}");

        $post = \get_post($id);

        $not_found = $this->postNotFoundIfMismatch($post, $post_type_slug);
        if ($not_found !== null) {
            return $not_found;
        }

        $post_data = $this->buildUpdatePostData((int) $id, $params);
        $result = \wp_update_post($post_data);

        if (\is_wp_error($result)) {
            return $result;
        }

        $this->maybeInvalidateCachesAfterUpdate($post_data);

        $this->applyMetaBundleFromParams((int) $id, $params, $post_type_slug);
        $this->applyTaxonomiesOnUpdate((int) $id, $post_type_slug, $params);
        $this->applyFeaturedMediaOnUpdate((int) $id, $params);

        $updated_post = \get_post($id);

        return \rest_ensure_response($this->formatter->formatPostForRest($updated_post));
    }

    /**
     * Delete custom post type item
     *
     * @param \WP_REST_Request $request Request object
     * @param string $post_type_slug Post type
     * @return \WP_REST_Response|WP_Error Response or error
     */
    public function deleteCustomPostTypeItem($request, $post_type_slug)
    {
        $id = $request->get_param('id');
        $post = \get_post($id);

        $not_found = $this->postNotFoundIfMismatch($post, $post_type_slug);
        if ($not_found !== null) {
            return $not_found;
        }

        $result = \wp_delete_post($id, true);

        if (!$result) {
            return new WP_Error('delete_failed', 'Failed to delete post', ['status' => 500]);
        }

        return \rest_ensure_response(['deleted' => true, 'previous' => $this->formatter->formatPostForRest($post)]);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    private function buildInsertPostData(array $params, string $post_type_slug): array
    {
        return [
            'post_type' => $post_type_slug,
            'post_title' => $params['title'] ?? '',
            'post_content' => $params['content'] ?? '',
            'post_status' => $params['status'] ?? 'draft',
            'post_excerpt' => $params['excerpt'] ?? '',
            'post_author' => $params['author'] ?? \get_current_user_id()
        ];
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    private function buildUpdatePostData(int $id, array $params): array
    {
        $post_data = ['ID' => $id];

        if (isset($params['title'])) {
            $post_data['post_title'] = $params['title'];
        }
        if (isset($params['content'])) {
            $post_data['post_content'] = $params['content'];
        }
        if (isset($params['status'])) {
            $post_data['post_status'] = $params['status'];
        }
        if (isset($params['excerpt'])) {
            $post_data['post_excerpt'] = $params['excerpt'];
        }

        return $post_data;
    }

    /**
     * @param array<string, mixed> $post_data
     */
    private function maybeInvalidateCachesAfterUpdate(array $post_data): void
    {
        $status_changed = isset($post_data['post_status']);
        $author_changed = isset($post_data['post_author']);
        $date_changed = isset($post_data['post_date']);

        if (!$status_changed && !$author_changed && !$date_changed) {
            return;
        }

        \update_option('fleekdash_invalidate_frontend_cache', time(), false);

        if (\class_exists('FleekDash\\Controllers\\StatsController')) {
            \FleekDash\Controllers\StatsController::invalidate_stats_caches_on_content_change();
        }
    }

    /**
     * @param \WP_Post|null $post
     */
    private function postNotFoundIfMismatch($post, string $post_type_slug): ?WP_Error
    {
        $valid = $post !== null && $post->post_type === $post_type_slug;
        if ($valid) {
            return null;
        }

        return new WP_Error('not_found', self::MESSAGE_POST_NOT_FOUND, ['status' => 404]);
    }

    /**
     * @param array<string, mixed> $params
     */
    private function applyMetaBundleFromParams(int $postId, array $params, string $postTypeSlug): void
    {
        $this->applyStructuredMetaFromParams($postId, $params, $postTypeSlug);
        $this->applyLooseMetaKeysFromParams($postId, $params, $postTypeSlug);
    }

    /**
     * @param array<string, mixed> $params
     */
    private function applyStructuredMetaFromParams(int $postId, array $params, string $postTypeSlug): void
    {
        if (empty($params['meta']) || !\is_array($params['meta'])) {
            return;
        }

        foreach ($params['meta'] as $key => $value) {
            $processed = $this->formatter->processVoxelMediaField($key, $value, $postTypeSlug);
            \update_post_meta($postId, $key, $processed);
        }
    }

    /**
     * @param array<string, mixed> $params
     */
    private function applyLooseMetaKeysFromParams(int $postId, array $params, string $postTypeSlug): void
    {
        foreach ($params as $key => $value) {
            if (\in_array($key, self::RESERVED_JSON_KEYS, true)) {
                continue;
            }

            if (!\taxonomy_exists($key)) {
                $processed = $this->formatter->processVoxelMediaField($key, $value, $postTypeSlug);
                \update_post_meta($postId, $key, $processed);
            }
        }
    }

    /**
     * @param array<string, mixed> $params
     */
    private function applyTaxonomiesOnCreate(int $postId, string $postTypeSlug, array $params): void
    {
        $taxonomies = \get_object_taxonomies($postTypeSlug);
        foreach ($taxonomies as $taxonomy) {
            if (!isset($params[$taxonomy]) || empty($params[$taxonomy])) {
                continue;
            }

            $terms = \is_array($params[$taxonomy]) ? $params[$taxonomy] : [$params[$taxonomy]];
            \wp_set_post_terms($postId, $terms, $taxonomy);
        }
    }

    /**
     * @param array<string, mixed> $params
     */
    private function applyTaxonomiesOnUpdate(int $postId, string $postTypeSlug, array $params): void
    {
        $taxonomies = \get_object_taxonomies($postTypeSlug);
        foreach ($taxonomies as $taxonomy) {
            if (!isset($params[$taxonomy])) {
                continue;
            }

            $terms = \is_array($params[$taxonomy]) ? $params[$taxonomy] : [$params[$taxonomy]];
            \wp_set_post_terms($postId, $terms, $taxonomy);
        }
    }

    /**
     * @param array<string, mixed> $params
     */
    private function applyCategoriesAndTagsOnCreate(int $postId, array $params): void
    {
        if (!empty($params['categories'])) {
            \wp_set_post_categories($postId, $params['categories']);
        }

        if (!empty($params['tags'])) {
            \wp_set_post_tags($postId, $params['tags']);
        }
    }

    /**
     * @param array<string, mixed> $params
     */
    private function applyFeaturedMediaOnCreate(int $postId, array $params): void
    {
        if (empty($params['featured_media'])) {
            return;
        }

        $resolved = $this->resolveFeaturedAttachmentId($params['featured_media']);
        if ($resolved !== null) {
            \set_post_thumbnail($postId, $resolved);
        }
    }

    /**
     * @param array<string, mixed> $params
     */
    private function applyFeaturedMediaOnUpdate(int $postId, array $params): void
    {
        if (!isset($params['featured_media'])) {
            return;
        }

        $featured_id = $params['featured_media'];

        if (\is_array($featured_id) && isset($featured_id['id'])) {
            $featured_id = $featured_id['id'];
        } elseif (\is_object($featured_id) && isset($featured_id->id)) {
            $featured_id = $featured_id->id;
        }

        if (\is_numeric($featured_id)) {
            \set_post_thumbnail($postId, $featured_id);
        } elseif (empty($featured_id)) {
            \delete_post_thumbnail($postId);
        }
    }

    /**
     * @param mixed $featured_raw
     */
    private function resolveFeaturedAttachmentId($featured_raw): ?int
    {
        $featured_id = $featured_raw;

        if (\is_array($featured_id) && isset($featured_id['id'])) {
            $featured_id = $featured_id['id'];
        } elseif (\is_object($featured_id) && isset($featured_id->id)) {
            $featured_id = $featured_id->id;
        }

        if (!\is_numeric($featured_id)) {
            return null;
        }

        return (int) $featured_id;
    }
}
