<?php

namespace FleekDash\Controllers\Post;

use FleekDash\Core\AuthMiddleware;
use FleekDash\Core\CacheInvalidationManager;
use FleekDash\Libs\API\ResponseService;
use FleekDash\Controllers\Post\PostFormatter;
use FleekDash\Controllers\Post\PostMetaProcessor;

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

/**
 * Handles post update logic extracted from PostController.
 * Reduces complexity and method count in the main controller.
 *
 * @package FleekDash\Controllers\Post
 */
class PostUpdateHandler
{
    private const MSG_POST_NOT_FOUND = 'Post not found';

    /**
     * Execute post update. Returns REST response (success or error).
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response
     */
    public static function execute(\WP_REST_Request $request): \WP_REST_Response
    {
        $params = self::mergeRequestParams($request);
        $validationError = self::validateRequest($request, $params);
        if ($validationError !== null) {
            return $validationError;
        }

        $result = self::performUpdate($request, $params);
        if ($result instanceof \WP_REST_Response) {
            return $result;
        }

        \clean_post_cache($result);
        $post = \get_post($result);
        return ResponseService::success(PostFormatter::formatPostForGrid($post), 'Post updated successfully');
    }

    /**
     * Merge request params (body JSON, query, fallback JSON string).
     *
     * @return array<string, mixed>
     */
    private static function mergeRequestParams(\WP_REST_Request $request): array
    {
        $params = $request->get_params();
        $json_params = $request->get_json_params();
        if (!empty($json_params) && is_array($json_params)) {
            $params = array_merge($params, $json_params);
        }
        if (isset($params['0']) && is_string($params['0'])) {
            $decoded = json_decode($params['0'], true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $params = array_merge($params, $decoded);
                unset($params['0']);
            }
        }
        return $params;
    }

    /**
     * Validate request and permissions. Returns error response or null.
     *
     * @return \WP_REST_Response|null
     */
    private static function validateRequest(\WP_REST_Request $request, array $params): ?\WP_REST_Response
    {
        $post_id = (int) $request->get_param('id');
        $post = \get_post($post_id);
        if (!$post) {
            return ResponseService::error(self::MSG_POST_NOT_FOUND, null, 404);
        }

        $auth_result = AuthMiddleware::check('authenticated');
        $can_edit = false;
        if ($auth_result['authenticated']) {
            $can_edit = \current_user_can('edit_' . $post->post_type) || \current_user_can('manage_options');
        } elseif (defined('WP_DEBUG') && WP_DEBUG) {
            $can_edit = true;
        }
        if (!$can_edit) {
            return ResponseService::error("You don't have permission to edit this post", null, 403);
        }

        return self::validateParentParam($post_id, $params, $post);
    }

    /**
     * Validate parent param. Returns error or null.
     *
     * @return \WP_REST_Response|null
     */
    private static function validateParentParam(int $post_id, array $params, $post): ?\WP_REST_Response
    {
        $parent_id = isset($params['parent']) && $params['parent'] !== null ? (int) $params['parent'] : 0;
        if ($parent_id <= 0 || $parent_id === $post_id) {
            return null;
        }
        $parent_post = \get_post($parent_id);
        if (!$parent_post || $parent_post->post_type !== $post->post_type) {
            return ResponseService::error('Invalid parent ID or parent is different post type', null, 400);
        }
        return null;
    }

    /**
     * Perform the update. Returns post_id or error response.
     *
     * @return int|\WP_REST_Response
     */
    private static function performUpdate(\WP_REST_Request $request, array $params)
    {
        $post_id = (int) $request->get_param('id');
        $post = \get_post($post_id);

        $post_data = self::buildPostData($post_id, $params);

        $dateError = self::applyDateUpdate($post_id, $params);
        if ($dateError !== null) {
            return $dateError;
        }

        self::applyFeaturedMediaToPostData($post_data, $params);

        $updated = self::runWpUpdatePost($post_data, $post_id);
        if ($updated instanceof \WP_REST_Response) {
            return $updated;
        }

        self::triggerCacheInvalidation($post_data, $params);
        self::applyMetaUpdates($post_id, $params);
        self::applyTaxonomyUpdates($post_id, $params, $post);

        return $updated;
    }

    /**
     * @return array<string, mixed>
     */
    private static function buildPostData(int $post_id, array $params): array
    {
        $post = \get_post($post_id);
        $post_data = ['ID' => $post_id];

        $fields = ['title' => 'post_title', 'content' => 'post_content', 'status' => 'post_status', 'excerpt' => 'post_excerpt', 'author' => 'post_author'];
        foreach ($fields as $param_key => $wp_key) {
            if (isset($params[$param_key]) && $params[$param_key] !== null) {
                $post_data[$wp_key] = $params[$param_key];
            }
        }

        if (isset($params['parent']) && $params['parent'] !== null) {
            $parent_id = (int) $params['parent'];
            if ($parent_id > 0 && $post) {
                $parent_post = \get_post($parent_id);
                if ($parent_post && $parent_post->post_type === $post->post_type) {
                    $post_data['post_parent'] = $parent_id;
                }
            } else {
                $post_data['post_parent'] = 0;
            }
        }

        return $post_data;
    }

    /**
     * Apply date update via direct DB. Returns error response or null.
     *
     * @return \WP_REST_Response|null
     */
    private static function applyDateUpdate(int $post_id, array $params): ?\WP_REST_Response
    {
        if (!isset($params['date']) || $params['date'] === null) {
            return null;
        }

        if (isset($params['date'])) {
            fleekdash_log_debug('PostUpdateHandler::applyDateUpdate - Received date: ' . $params['date']);
        }

        global $wpdb;
        $date_to_update = $params['date'];
        if (strpos($date_to_update, 'T') !== false) {
            $date_to_update = str_replace('T', ' ', $date_to_update);
            $date_to_update = preg_replace('/[+\-]\d{2}:\d{2}$|Z$/', '', $date_to_update);
        }

        $date_gmt = get_gmt_from_date($date_to_update);
        $post_before = \get_post($post_id);
        $next_status = $post_before
            ? self::resolveStatusAfterDateChange($date_to_update, (string) $post_before->post_status)
            : null;

        $date_row = [
            'post_date' => $date_to_update,
            'post_date_gmt' => $date_gmt,
        ];
        $date_formats = ['%s', '%s'];
        if ($next_status !== null) {
            $date_row['post_status'] = $next_status;
            $date_formats[] = '%s';
        }

        $update_result = $wpdb->update(
            $wpdb->posts,
            $date_row,
            ['ID' => $post_id],
            $date_formats,
            ['%d']
        );

        if ($update_result === false) {
            fleekdash_log_error('PostUpdateHandler::applyDateUpdate - Failed: ' . $wpdb->last_error);
            return ResponseService::error('Failed to update post date: ' . $wpdb->last_error, null, 500);
        }

        clean_post_cache($post_id);
        $modified = \current_time('mysql');
        $modified_gmt = \current_time('mysql', 1);
        $wpdb->update(
            $wpdb->posts,
            ['post_modified' => $modified, 'post_modified_gmt' => $modified_gmt],
            ['ID' => $post_id],
            ['%s', '%s'],
            ['%d']
        );

        return null;
    }

    /**
     * WordPress scheduled posts use status "future" when post_date is in the future.
     * Date-only grid updates must set this or reload shows the old status (e.g. publish).
     *
     * @return string|null New post_status for DB, or null to leave status unchanged.
     */
    private static function resolveStatusAfterDateChange(string $date_mysqlLocal, string $currentStatus): ?string
    {
        $result = null;
        $immutable = in_array($currentStatus, ['trash', 'inherit', 'auto-draft'], true);
        $newTs = $immutable ? false : strtotime($date_mysqlLocal);

        if ($newTs !== false) {
            $nowTs = \current_time('timestamp');
            $isFuture = $newTs > $nowTs;
            if ($isFuture && $currentStatus !== 'future') {
                $result = 'future';
            } elseif (!$isFuture && $currentStatus === 'future') {
                $result = 'publish';
            }
        }

        return $result;
    }

    /**
     * @param array<string, mixed> $post_data
     */
    private static function applyFeaturedMediaToPostData(array &$post_data, array $params): void
    {
        if (!isset($params['featured_media']) || $params['featured_media'] === null) {
            return;
        }
        $featured_media = $params['featured_media'];
        if (is_array($featured_media) && isset($featured_media['attachment_id'])) {
            $post_data['meta_input'] = $post_data['meta_input'] ?? [];
            $post_data['meta_input']['_thumbnail_id'] = $featured_media['attachment_id'];
        } elseif (is_numeric($featured_media)) {
            $post_data['meta_input'] = $post_data['meta_input'] ?? [];
            $post_data['meta_input']['_thumbnail_id'] = (int) $featured_media;
        }
    }

    /**
     * Run wp_update_post and ensure status if needed.
     *
     * @param array<string, mixed> $post_data
     * @return int|\WP_REST_Response
     */
    private static function runWpUpdatePost(array $post_data, int $post_id)
    {
        $updated = $post_id;

        if (count($post_data) > 1) {
            $updated = \wp_update_post($post_data, true);

            if (is_wp_error($updated)) {
                return ResponseService::error('Failed to update post: ' . $updated->get_error_message());
            }

            if (isset($post_data['post_status'])) {
                \clean_post_cache($post_id);
                $fresh = \get_post($post_id);
                if ($fresh && $fresh->post_status !== $post_data['post_status']) {
                    global $wpdb;
                    fleekdash_log_warn("PostUpdateHandler - wp_update_post did not update status. Fallback DB update.");
                    $db_ok = $wpdb->update(
                        $wpdb->posts,
                        ['post_status' => $post_data['post_status']],
                        ['ID' => $post_id],
                        ['%s'],
                        ['%d']
                    );
                    if ($db_ok === false) {
                        return ResponseService::error('Failed to update post status: ' . $wpdb->last_error);
                    }
                    \clean_post_cache($post_id);
                }
            }
        }

        return $updated;
    }

    /**
     * @param array<string, mixed> $post_data
     */
    private static function triggerCacheInvalidation(array $post_data, array $params): void
    {
        $has_status = isset($post_data['post_status']);
        $has_author = isset($post_data['post_author']);
        $has_date = isset($post_data['post_date']) || (isset($params['date']) && $params['date'] !== null);

        if ($has_status || $has_author || $has_date) {
            \update_option('fleekdash_invalidate_frontend_cache', time(), false);
            if (class_exists('FleekDash\\Controllers\\StatsController')) {
                \FleekDash\Controllers\StatsController::invalidate_stats_caches_on_content_change();
            }
            // Date-only paths use $wpdb and skip wp_insert_post — flush list + REST ETag transients
            // immediately so the next GET is not 304 / stale (browser cache amplifies the issue).
            CacheInvalidationManager::getInstance()->flushPostsListCachesNow();
        }
    }

    private static function applyMetaUpdates(int $post_id, array $params): void
    {
        $meta = $params['meta'] ?? null;
        if (!empty($meta) && is_array($meta)) {
            foreach ($meta as $meta_key => $meta_value) {
                if (strpos($meta_key, '_') === 0) {
                    continue;
                }
                $processed = PostMetaProcessor::processMetaFieldValue($meta_key, $meta_value);
                \update_post_meta($post_id, $meta_key, $processed);
            }
        }

        $core_fields = ['id', 'title', 'content', 'status', 'excerpt', 'author', 'taxonomies', 'meta', 'featured_media'];
        foreach ($params as $param_key => $param_value) {
            if (
                !in_array($param_key, $core_fields) &&
                strpos($param_key, '_') !== 0 &&
                !empty($param_value)
            ) {
                $processed = PostMetaProcessor::processMetaFieldValue($param_key, $param_value);
                \update_post_meta($post_id, $param_key, $processed);
            }
        }
    }

    private static function applyTaxonomyUpdates(int $post_id, array $params, $post): void
    {
        $taxonomies = $params['taxonomies'] ?? null;
        if (!empty($taxonomies) && is_array($taxonomies)) {
            foreach ($taxonomies as $taxonomy => $terms) {
                PostMetaProcessor::processTaxonomyTerms($post_id, $taxonomy, $terms);
            }
        }

        $post_type_taxonomies = get_object_taxonomies($post->post_type);
        foreach ($post_type_taxonomies as $taxonomy) {
            if (isset($params[$taxonomy]) && !empty($params[$taxonomy])) {
                PostMetaProcessor::processTaxonomyTerms($post_id, $taxonomy, $params[$taxonomy]);
            }
        }
    }
}
