<?php
/**
 * This file is part of the Wisewand WordPress plugin.
 *
 * @package Wisewand
 * @since 2.8.2
 * @license GPL-2.0-or-later
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

namespace Wisewand\Automation;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Wisewand\Utils\Options;
use Wisewand\Logging\Logger;
use Wisewand\Content\Content_Manager;
use Wisewand\Utils\Rate_Limiter;

// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- error_log() and print_r() are only used within WP_DEBUG conditionals for debugging; table names derived from $wpdb->prefix are safe to interpolate

/**
 * CRON Article Importer
 *
 * Phase 3 of the CRON pipeline: imports Wisewand-generated articles into
 * WordPress, handles scheduling, and publishes scheduled RSS articles.
 * Also exposes import_and_publish_entry() for use by CRON_Retry_Handler.
 *
 * @package Wisewand
 * @since 2.8.2
 */
class CRON_Article_Importer {

	/**
	 * Options wrapper.
	 *
	 * @var Options
	 */
	private $options;

	/**
	 * Logger instance.
	 *
	 * @var Logger
	 */
	private $logger;

	/**
	 * Wisewand API client.
	 *
	 * @var \Wisewand\Utils\Wisewand_API_Client|null
	 */
	private $wisewand_api;

	/**
	 * Content manager.
	 *
	 * @var Content_Manager
	 */
	private $content_manager;

	/**
	 * Calendar entry manager.
	 *
	 * @var \Wisewand\Calendar\Calendar_Entry_Manager
	 */
	private $entry_manager;

	/**
	 * Rate limiter instance.
	 *
	 * @var Rate_Limiter
	 */
	private $rate_limiter;

	/**
	 * Article generator for determine_endpoint() reuse.
	 *
	 * @var CRON_Article_Generator
	 */
	private $generator;

	/**
	 * Shared run summary array (passed by reference from the facade).
	 *
	 * @var array
	 */
	private $run_summary = array();

	/**
	 * Constructor.
	 *
	 * @since 2.8.2
	 *
	 * @param Options                $options         Plugin options wrapper.
	 * @param Logger                 $logger          Logger instance.
	 * @param mixed                  $wisewand_api    Wisewand API client or null.
	 * @param Content_Manager        $content_manager Content manager instance.
	 * @param mixed                  $entry_manager   Calendar entry manager instance.
	 * @param Rate_Limiter           $rate_limiter    Rate limiter instance.
	 * @param CRON_Article_Generator $generator       Generator instance for endpoint resolution.
	 */
	public function __construct(
		Options $options,
		Logger $logger,
		$wisewand_api,
		Content_Manager $content_manager,
		$entry_manager,
		Rate_Limiter $rate_limiter,
		CRON_Article_Generator $generator
	) {
		$this->options         = $options;
		$this->logger          = $logger;
		$this->wisewand_api    = $wisewand_api;
		$this->content_manager = $content_manager;
		$this->entry_manager   = $entry_manager;
		$this->rate_limiter    = $rate_limiter;
		$this->generator       = $generator;
	}

	/**
	 * Set the shared run summary by reference.
	 *
	 * Called by the facade immediately before invoking any step method so that
	 * counters incremented here are visible in the facade's own summary.
	 *
	 * @since 2.8.2
	 *
	 * @param array $summary Run summary array passed by reference.
	 */
	public function set_run_summary( array &$summary ) {
		$this->run_summary = &$summary;
	}

	/**
	 * Step 3 for 15-minute CRON: import all ready articles.
	 *
	 * Queries all calendar entries in 'ready' status that have not yet been
	 * imported (wordpress_post_id is null/0) and imports each one, assigning a
	 * random publication time when none is stored.
	 *
	 * @since 2.8.2
	 */
	public function step_3_import_ready_articles() {
		$today = gmdate( 'Y-m-d' );
		global $wpdb;
		$table = $wpdb->prefix . 'wisewand_editorial_calendar';

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '=== CRON STEP 3 (15-min): Import Ready Articles ===' );
			error_log( 'CRON Step 3: Today: ' . $today . ', Current time: ' . current_time( 'mysql' ) );
		}

		// Get ALL ready entries regardless of date.
		// This enables "Immediate Import" -> "Native WordPress Scheduling".
		// Exclude already imported articles.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for ready articles to import
		$to_import = $wpdb->get_results(
			"SELECT * FROM {$table}
             WHERE wisewand_status = 'ready'
             AND (wordpress_post_id IS NULL OR wordpress_post_id = 0)
             AND (wordpress_status = 'scheduled' OR wordpress_status IS NULL)
             ORDER BY publication_date ASC, publication_time ASC",
			ARRAY_A
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3: Found ' . count( $to_import ) . ' ready articles due for publication (today or past-due)' );
		}

		if ( empty( $to_import ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 3: No articles to import at this time' );
			}
			return;
		}

		// Log per-entry details.
		foreach ( $to_import as $entry ) {
			$days_overdue = ( strtotime( $today ) - strtotime( $entry['publication_date'] ) ) / 86400;
			$status       = $days_overdue > 0 ? 'OVERDUE by ' . floor( $days_overdue ) . ' days' : 'DUE TODAY';
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '  Entry #' . $entry['id'] . ': ' . $entry['keyword'] . ' - ' . $status );
				error_log( '    Publication date: ' . $entry['publication_date'] );
				error_log( '    Publication time: ' . ( $entry['publication_time'] ?? 'NOT SET' ) );
			}
		}

		$imported_count = 0;
		foreach ( $to_import as $entry ) {
			// Ensure publication time is set.
			if ( empty( $entry['publication_time'] ) ) {
				$random_time = $this->generate_random_time( $entry['publication_time_range'] ?? 'random' );
				$this->entry_manager->update_entry(
					$entry['id'],
					array( 'publication_time' => $random_time )
				);
				$entry['publication_time'] = $random_time;
			}

			// Calculate full target datetime.
			$scheduled_datetime = $entry['publication_date'] . ' ' . $entry['publication_time'];

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '  Entry #' . $entry['id'] . ' - Ready for immediate import (Scheduled: ' . $scheduled_datetime . ')' );
			}

			// Pass scheduled date to import function for native WP scheduling.
			$result = $this->import_and_publish_entry( $entry, $scheduled_datetime );

			if ( ! is_wp_error( $result ) ) {
				++$imported_count;
				++$this->run_summary['imported'];
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( '  Entry #' . $entry['id'] . ' imported successfully' );
				}
			} elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '  Entry #' . $entry['id'] . ' import failed: ' . $result->get_error_message() );
			}

			$this->rate_limiter->smart_delay( 'wisewand_import', 2.0 );
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3: Imported ' . $imported_count . ' articles' );
			error_log( '=== CRON STEP 3 (15-min): Complete ===' );
		}
	}

	/**
	 * Step 3 for daily CRON: full import and publish with diagnostics.
	 *
	 * This is the legacy method with comprehensive logging kept for the daily
	 * (2 AM) CRON run and for manual triggering via WP-CLI or admin UI.
	 *
	 * @since 2.8.2
	 */
	public function step_3_import_and_publish() {
		$today = gmdate( 'Y-m-d' );

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '=== CRON STEP 3 (Daily): Import and Publish ===' );
			error_log( 'CRON Step 3: Looking for entries to import' );
			error_log( 'CRON Step 3: Today date: ' . $today );
			error_log( 'CRON Step 3: Current time: ' . current_time( 'mysql' ) );
		}

		global $wpdb;
		$table = $wpdb->prefix . 'wisewand_editorial_calendar';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for all ready articles diagnostic
		$all_ready = $wpdb->get_results(
			"SELECT id, keyword, publication_date, publication_time,
                    wisewand_status, wordpress_status, wordpress_post_id
             FROM {$table}
             WHERE wisewand_status = 'ready'
             ORDER BY publication_date ASC, publication_time ASC",
			ARRAY_A
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3: Found ' . count( $all_ready ) . ' entries with wisewand_status=ready' );
		}

		if ( ! empty( $all_ready ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 3: Details of ready entries:' );
			}

			// ONE-TIME FIX: Update any ready entries that don't have wordpress_status set.
			$fixed_count = 0;
			foreach ( $all_ready as $entry ) {
				$already_imported = ! empty( $entry['wordpress_post_id'] ) ? 'YES (post_id: ' . $entry['wordpress_post_id'] . ')' : 'NO';
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( '  Entry #' . $entry['id'] . ': ' . $entry['keyword'] );
					error_log( '    publication_date: ' . $entry['publication_date'] . ' (today: ' . $today . ')' );
					error_log( '    publication_time: ' . ( $entry['publication_time'] ?? 'NOT SET' ) );
					error_log( '    wordpress_status: ' . $entry['wordpress_status'] );
					error_log( '    already_imported: ' . $already_imported );
				}

				// Fix entries that are ready but wordpress_status is not 'scheduled' and not imported yet.
				// IMPORTANT: Do NOT reset imported statuses — only fix entries stuck in invalid states.
				$invalid_states  = array( 'importing', 'failed' );
				$imported_states = array( 'imported_draft', 'imported_published' );

				if ( empty( $entry['wordpress_post_id'] )
					&& 'scheduled' !== $entry['wordpress_status']
					&& ! in_array( $entry['wordpress_status'], $imported_states, true ) ) {

					// Only reset if in invalid state, not if it's a legitimate non-scheduled state.
					if ( in_array( $entry['wordpress_status'], $invalid_states, true ) || empty( $entry['wordpress_status'] ) ) {
						if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
							error_log( '  FIXING: Setting wordpress_status=scheduled for entry #' . $entry['id'] . ' (was: ' . $entry['wordpress_status'] . ')' );
						}
						// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table update for fixing wordpress_status
						$wpdb->update(
							$table,
							array( 'wordpress_status' => 'scheduled' ),
							array( 'id' => $entry['id'] ),
							array( '%s' ),
							array( '%d' )
						);
						++$fixed_count;
					}
				}
			}

			if ( $fixed_count > 0 && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 3: Fixed ' . $fixed_count . ' ready entries by setting wordpress_status=scheduled' );
			}
		}

		// Restrictive query: today + ready + scheduled.
		$to_publish = $this->entry_manager->get_entries(
			array(
				'publication_date' => $today,
				'wisewand_status'  => 'ready',
				'wordpress_status' => 'scheduled',
			)
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3: Restrictive query (today + ready + scheduled) found: ' . count( $to_publish ) . ' entries' );
		}

		if ( count( $to_publish ) === 0 && ! empty( $all_ready ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 3: WARNING - Ready entries exist but none match wordpress_status=scheduled' );
				error_log( 'CRON Step 3: This is likely why imports are not happening!' );
			}

			// Try broader query — any ready entry for today that hasn't been imported.
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for broader ready articles check
			$to_publish_broader = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM {$table}
                 WHERE publication_date = %s
                 AND wisewand_status = 'ready'
                 AND (wordpress_post_id IS NULL OR wordpress_post_id = 0)
                 ORDER BY publication_time ASC",
					$today
				),
				ARRAY_A
			);

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 3: Broader query (today + ready + not imported) found: ' . count( $to_publish_broader ) . ' entries' );
			}

			if ( count( $to_publish_broader ) > 0 ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 3: Using broader query results to import entries' );
				}
				$to_publish = $to_publish_broader;
			}
		}

		$this->logger->info(
			'Step 3: Import and publish',
			array(
				'today'           => $today,
				'count'           => count( $to_publish ),
				'all_ready_count' => count( $all_ready ),
			)
		);

		if ( empty( $to_publish ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 3: No entries to import' );
				error_log( '=== CRON STEP 3: Complete ===' );
			}
			return;
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3: Processing ' . count( $to_publish ) . ' entries for import' );
		}

		foreach ( $to_publish as $entry ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 3: Checking entry #' . $entry['id'] . ' - ' . $entry['keyword'] );
				error_log( '  Publication time: ' . ( $entry['publication_time'] ?? 'NOT SET' ) );
				error_log( '  Publication time range: ' . ( $entry['publication_time_range'] ?? 'NOT SET' ) );
			}

			if ( ! $this->is_time_to_publish( $entry ) ) {
				$pub_time = $entry['publication_time'] ?? 'NOT SET';
				$now      = current_time( 'mysql' );
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( '  NOT time to publish yet (scheduled: ' . $pub_time . ', now: ' . $now . ')' );
				}
				continue;
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '  Time to publish! Starting import...' );
			}
			$this->import_and_publish_entry( $entry );
			++$this->run_summary['imported'];
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '  Import completed for entry #' . $entry['id'] );
			}

			$this->rate_limiter->smart_delay( 'wisewand_import', 2.0 );
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3: Imported ' . $this->run_summary['imported'] . ' entries' );
			error_log( '=== CRON STEP 3: Complete ===' );
		}
	}

	/**
	 * Step 3.5: Publish scheduled RSS articles (RSS v2.0).
	 *
	 * Checks RSS articles that are imported as drafts and waiting for their
	 * scheduled publication time. When scheduled_publish_time is reached,
	 * publishes the article. Respects publication time restrictions and allowed
	 * days configured in Calendar or per-feed settings.
	 *
	 * @since 2.8.2
	 */
	public function step_3_5_publish_scheduled_rss_articles() {
		global $wpdb;
		$articles_table = $wpdb->prefix . 'wisewand_rss_articles';
		$now            = current_time( 'mysql' );

		// Diagnostic logging to identify which CRON condition is blocking publication.
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Diagnostic query only runs under WP_DEBUG
			$status_breakdown = $wpdb->get_results(
				"SELECT
					import_status,
					wordpress_post_status,
					COUNT(*) as count
				FROM {$articles_table}
				GROUP BY import_status, wordpress_post_status",
				ARRAY_A
			);

			$this->logger->debug(
				'CRON_PUBLISH_STATUS: Article status breakdown',
				array( 'breakdown' => $status_breakdown )
			);

			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Diagnostic counts only run under WP_DEBUG
			$cond1 = $wpdb->get_var( "SELECT COUNT(*) FROM {$articles_table} WHERE import_status != 'imported'" );
			$cond2 = $wpdb->get_var( "SELECT COUNT(*) FROM {$articles_table} WHERE wordpress_post_status != 'draft'" );
			$cond3 = $wpdb->get_var( "SELECT COUNT(*) FROM {$articles_table} WHERE scheduled_publish_time IS NULL" );
			$cond4 = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$articles_table} WHERE scheduled_publish_time > %s", current_time( 'mysql' ) ) );
			$cond5 = $wpdb->get_var( "SELECT COUNT(*) FROM {$articles_table} WHERE wordpress_post_id IS NULL OR wordpress_post_id = 0" );
			// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

			$this->logger->debug(
				'CRON_PUBLISH_CONDITIONS: Articles failing each condition',
				array(
					'not_imported'       => $cond1,
					'not_draft'          => $cond2,
					'no_schedule'        => $cond3,
					'schedule_in_future' => $cond4,
					'no_post_id'         => $cond5,
				)
			);
		}

		// Find RSS articles ready to publish.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for scheduled RSS articles
		$ready_to_publish = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$articles_table}
             WHERE import_status = 'imported'
             AND wordpress_post_status = 'draft'
             AND scheduled_publish_time IS NOT NULL
             AND scheduled_publish_time <= %s
             AND wordpress_post_id IS NOT NULL
             AND wordpress_post_id > 0",
				$now
			),
			ARRAY_A
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3.5 (RSS Publish): Checking for scheduled RSS articles to publish' );
			error_log( 'CRON Step 3.5: Current time: ' . $now );
			error_log( 'CRON Step 3.5: Found ' . count( $ready_to_publish ) . ' RSS article(s) ready to publish' );
		}

		if ( empty( $ready_to_publish ) ) {
			return;
		}

		$published_count = 0;
		$failed_count    = 0;

		foreach ( $ready_to_publish as $article ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				$this->logger->debug(
					'CRON_PUBLISH_ATTEMPT: Processing article for publication',
					array(
						'article_id'        => $article['id'],
						'wordpress_post_id' => $article['wordpress_post_id'],
						'scheduled_time'    => $article['scheduled_publish_time'],
						'current_time'      => $now,
						'current_status'    => $article['wordpress_post_status'],
						'title'             => $article['article_title'],
					)
				);
			}

			try {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 3.5: Publishing RSS article #' . $article['wordpress_post_id'] . ' - ' . $article['article_title'] );
					error_log( 'CRON Step 3.5: Scheduled time: ' . $article['scheduled_publish_time'] . ', Current: ' . $now );
				}

				// VALIDATION: Check post exists before attempting to publish.
				$post = get_post( $article['wordpress_post_id'] );
				if ( ! $post ) {
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'CRON Step 3.5: Post #' . $article['wordpress_post_id'] . ' not found (deleted?), cleaning up tracking' );
					}

					// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Update tracking when post no longer exists
					$wpdb->update(
						$articles_table,
						array(
							'wordpress_post_status' => 'deleted',
							'import_status'         => 'failed',
							'import_error'          => 'WordPress post was deleted',
						),
						array( 'id' => $article['id'] ),
						array( '%s', '%s', '%s' ),
						array( '%d' )
					);

					$this->logger->warning(
						'RSS article post not found (deleted?), cleaned up tracking',
						array(
							'article_id'        => $article['id'],
							'wordpress_post_id' => $article['wordpress_post_id'],
							'article_title'     => $article['article_title'],
						)
					);

					++$failed_count;
					continue;
				}

				// VALIDATION: Verify post is in draft status.
				if ( 'draft' !== $post->post_status ) {
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'CRON Step 3.5: Post #' . $article['wordpress_post_id'] . ' is already "' . $post->post_status . '", skipping' );
					}

					// Update tracking to match actual post status.
					// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Sync tracking with actual post status
					$wpdb->update(
						$articles_table,
						array( 'wordpress_post_status' => $post->post_status ),
						array( 'id' => $article['id'] ),
						array( '%s' ),
						array( '%d' )
					);

					continue;
				}

				// Publish the WordPress post.
				$updated = wp_update_post(
					array(
						'ID'          => $article['wordpress_post_id'],
						'post_status' => 'publish',
					),
					true
				);

				if ( is_wp_error( $updated ) ) {
					throw new \Exception( $updated->get_error_message() );
				}

				if ( ! $updated || 0 === $updated ) {
					throw new \Exception( 'Failed to update post status' );
				}

				// Update tracking in database.
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Update tracking after successful publish
				$wpdb->update(
					$articles_table,
					array( 'wordpress_post_status' => 'publish' ),
					array( 'id' => $article['id'] ),
					array( '%s' ),
					array( '%d' )
				);

				// Sync published status to Wisewand API.
				if ( ! class_exists( 'Wisewand\\Utils\\Wisewand_API_Client' ) ) {
					require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/Wisewand_API_Client.php';
				}

				$api_client = new \Wisewand\Utils\Wisewand_API_Client( $this->options->get( 'wisewand_api_key' ), $this->logger );
				$api_client->update_article( $article['wisewand_article_id'], array( 'is_published' => true ) );

				++$published_count;

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 3.5: Published RSS article #' . $article['wordpress_post_id'] . ' successfully' );
				}
			} catch ( \Exception $e ) {
				++$failed_count;

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 3.5: Failed to publish article #' . $article['wordpress_post_id'] . ': ' . $e->getMessage() );
				}

				$this->logger->error(
					'Failed to publish scheduled RSS article',
					array(
						'article_id'        => $article['id'],
						'wordpress_post_id' => $article['wordpress_post_id'],
						'error'             => $e->getMessage(),
					)
				);
			}
		}

		$this->logger->info(
			'Step 3.5: Published scheduled RSS articles',
			array(
				'ready_to_publish' => count( $ready_to_publish ),
				'published'        => $published_count,
				'failed'           => $failed_count,
			)
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3.5: Summary - Published: ' . $published_count . ', Failed: ' . $failed_count );
		}
	}

	/**
	 * Check whether the current time has reached a calendar entry's publish time.
	 *
	 * If no publication_time is stored, generates a random time within the
	 * entry's configured range and persists it before comparing.
	 *
	 * @since 2.8.2
	 *
	 * @param array $entry Calendar entry row.
	 * @return bool True if it is time to publish, false otherwise.
	 */
	public function is_time_to_publish( $entry ) {
		if ( empty( $entry['publication_time'] ) ) {
			$random_time = $this->generate_random_time( $entry['publication_time_range'] ?? 'random' );

			$this->entry_manager->update_entry(
				$entry['id'],
				array( 'publication_time' => $random_time )
			);

			$entry['publication_time'] = $random_time;
		}

		$publish_datetime = strtotime( $entry['publication_date'] . ' ' . $entry['publication_time'] );
		$current_time     = current_time( 'timestamp' );

		return $current_time >= $publish_datetime;
	}

	/**
	 * Generate a random time within a named range.
	 *
	 * Accepts 'random', 'morning', 'afternoon', or 'evening'. Falls back to
	 * 'random' (09:00–20:00) for unknown range values.
	 *
	 * @since 2.8.2
	 *
	 * @param string $range Named time range.
	 * @return string Time string in H:i:s format.
	 */
	public function generate_random_time( $range ) {
		$ranges = array(
			'random'    => array( '09:00:00', '20:00:00' ),
			'morning'   => array( '09:00:00', '12:00:00' ),
			'afternoon' => array( '12:00:00', '17:00:00' ),
			'evening'   => array( '17:00:00', '20:00:00' ),
		);

		[ $start, $end ] = $ranges[ $range ] ?? $ranges['random'];

		$start_seconds  = strtotime( $start ) - strtotime( '00:00:00' );
		$end_seconds    = strtotime( $end ) - strtotime( '00:00:00' );
		$random_seconds = wp_rand( $start_seconds, $end_seconds );

		return gmdate( 'H:i:s', $random_seconds );
	}

	/**
	 * Import a single calendar entry and create/schedule the WordPress post.
	 *
	 * Uses the shared Content_Importer utility (identical logic to the manual
	 * "Import Now" UI flow). On success updates the calendar entry with the
	 * resulting post ID and final status. On failure marks the entry as
	 * 'failed' and returns a WP_Error rather than throwing.
	 *
	 * @since 2.8.2
	 *
	 * @param array       $entry          Calendar entry row.
	 * @param string|null $scheduled_date Optional full datetime for native WP scheduling (e.g. '2025-03-18 14:30:00').
	 * @return array|\WP_Error Import result array on success, WP_Error on failure.
	 */
	public function import_and_publish_entry( $entry, $scheduled_date = null ) {
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '=== IMPORT ENTRY #' . $entry['id'] . ' START ===' );
			error_log( 'IMPORT: Entry keyword: ' . $entry['keyword'] );
			error_log( 'IMPORT: Article ID: ' . $entry['wisewand_article_id'] );
			error_log( 'IMPORT: Entry type: ' . ( $entry['wisewand_type'] ?? 'NOT SET' ) );
		}

		if ( ! $this->wisewand_api ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: No Wisewand API client available!' );
			}
			return new \WP_Error( 'no_api_client', __( 'No Wisewand API client available', 'wisewand-ai-content-writer-seo-content-generator' ) );
		}

		try {
			// Load Content_Importer utility (shared with manual import).
			if ( ! class_exists( 'Wisewand\\Utils\\Content_Importer' ) ) {
				require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/Content_Importer.php';
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Step 1 - Updating entry status to importing' );
			}
			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wordpress_status'  => 'importing',
					'import_started_at' => current_time( 'mysql' ),
				)
			);
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Status updated to importing' );
			}

			// Prepare import options from calendar entry.
			// Use global default_post_status setting.
			$default_status = $this->options->get( 'wisewand_default_post_status', 'draft' );
			$import_options = array(
				'post_status' => $default_status,
			);

			// NATIVE SCHEDULING LOGIC: If a specific scheduled date is provided, use it.
			if ( ! empty( $scheduled_date ) ) {
				$import_options['post_date'] = $scheduled_date;

				// If the date is in the future, force status to 'future' (Scheduled).
				// This overrides the default post status preference.
				if ( strtotime( $scheduled_date ) > current_time( 'timestamp' ) ) {
					$import_options['post_status'] = 'future';

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'IMPORT: Future date detected (' . $scheduled_date . ') - Setting status to FUTURE' );
					}
				}
			}

			// Add author if specified.
			if ( ! empty( $entry['created_by'] ) ) {
				$import_options['author_id'] = $entry['created_by'];
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'IMPORT: Author from entry: ' . $entry['created_by'] );
				}
			}

			// Add categories if specified.
			if ( ! empty( $entry['wordpress_categories'] ) ) {
				$categories = is_array( $entry['wordpress_categories'] )
					? $entry['wordpress_categories']
					: json_decode( $entry['wordpress_categories'], true );

				if ( ! empty( $categories ) && is_array( $categories ) ) {
					$import_options['categories'] = $categories;
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'IMPORT: Categories from entry: ' . implode( ', ', $categories ) );
					}
				}
			}

			// Determine content type for API using the generator's method.
			$content_type = $this->generator->determine_endpoint( $entry['wisewand_type'] ?? 'blog' );
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Step 2 - Content type determined: ' . $content_type );
				error_log( 'IMPORT: Import options: ' . wp_json_encode( $import_options ) );
			}

			// Use shared import utility — SAME LOGIC AS MANUAL IMPORT.
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Step 3 - Calling Content_Importer::import_content()...' );
			}
			$result = \Wisewand\Utils\Content_Importer::import_content(
				$entry['wisewand_article_id'],
				$content_type,
				$import_options,
				$this->wisewand_api,
				$this->content_manager
			);

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Step 4 - Content_Importer returned' );
			}

			if ( is_wp_error( $result ) ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'IMPORT: Content_Importer returned WP_Error: ' . $result->get_error_message() );
				}
				throw new \Exception( $result->get_error_message() );
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Content_Importer succeeded' );
				error_log( 'IMPORT: Result keys: ' . ( is_array( $result ) ? implode( ', ', array_keys( $result ) ) : 'NOT ARRAY' ) );
			}

			// Verify import completed correctly.
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Step 5 - Verifying import...' );
			}
			$verification = \Wisewand\Utils\Content_Importer::verify_import( $result, $entry['wisewand_article_id'] );

			if ( ! $verification['success'] ) {
				$error_msg = 'Import verification failed: ' . implode( ', ', $verification['errors'] );
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'IMPORT: ' . $error_msg );
				}
				throw new \Exception( $error_msg );
			}

			if ( ! empty( $verification['warnings'] ) && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Warnings - ' . implode( ', ', $verification['warnings'] ) );
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Verification passed' );
			}

			// Extract post_id or category_id.
			$post_id     = $result['post_id'] ?? null;
			$category_id = $result['category_id'] ?? null;

			if ( ! $post_id && ! $category_id ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'IMPORT: No post_id or category_id in result' );
				}
				throw new \Exception( 'No post_id or category_id returned after import' );
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Post/Category ID obtained: ' . ( $post_id ?? $category_id ) );
			}

			// Determine sub-status based on actual WordPress post status.
			$actual_post_status = $result['post_status'] ?? null;

			if ( 'publish' === $actual_post_status ) {
				$final_status = 'imported_published';
			} elseif ( 'future' === $actual_post_status ) {
				$final_status = 'imported_scheduled';
			} elseif ( 'draft' === $actual_post_status || 'pending' === $actual_post_status ) {
				$final_status = 'imported_draft';
			} else {
				// Fallback to default_post_status if post_status not available.
				$default_status = $this->options->get( 'wisewand_default_post_status', 'draft' );
				$final_status   = ( 'publish' === $default_status ) ? 'imported_published' : 'imported_draft';
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Step 6 - Updating entry in calendar' );
				error_log( 'IMPORT: actual_post_status from result: ' . ( $actual_post_status ?? 'NOT SET' ) );
				error_log( 'IMPORT: final_status: ' . $final_status );
				error_log( 'IMPORT: default_post_status setting: ' . $this->options->get( 'wisewand_default_post_status', 'draft' ) );
			}

			$update_data = array(
				'wordpress_status' => $final_status,
				'published_at'     => current_time( 'mysql' ),
			);

			if ( $post_id ) {
				$update_data['wordpress_post_id'] = $post_id;
			}

			// CRITICAL: Check if status update succeeds.
			$update_result = $this->entry_manager->update_entry( $entry['id'], $update_data );

			if ( is_wp_error( $update_result ) ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'IMPORT: Failed to update calendar entry status!' );
					error_log( 'IMPORT: Error: ' . $update_result->get_error_message() );
					error_log( 'IMPORT: Update data: ' . wp_json_encode( $update_data ) );
				}
				throw new \Exception( 'Failed to update calendar entry status: ' . $update_result->get_error_message() );
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT: Entry updated in calendar with status: ' . $final_status );
				error_log( 'IMPORT: wordpress_post_id saved: ' . ( $post_id ?? 'NONE' ) );
			}

			$this->logger->info(
				'Content imported successfully',
				array(
					'entry_id'    => $entry['id'],
					'post_id'     => $post_id,
					'category_id' => $category_id,
					'status'      => $final_status,
				)
			);

			// Sync published status to Wisewand API.
			if ( ! empty( $entry['wisewand_article_id'] ) ) {
				$is_published = ( 'imported_published' === $final_status );
				$this->update_wisewand_published_status( $entry['wisewand_article_id'], $is_published );
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '=== IMPORT ENTRY #' . $entry['id'] . ' SUCCESS ===' );
				error_log( '' );
			}

			return $result;

		} catch ( \Exception $e ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'IMPORT EXCEPTION' );
				error_log( 'Entry #' . $entry['id'] . ' - ' . $entry['keyword'] );
				error_log( 'Error: ' . $e->getMessage() );
				error_log( 'Stack trace: ' . $e->getTraceAsString() );
				error_log( '=== IMPORT ENTRY #' . $entry['id'] . ' FAILED ===' );
				error_log( '' );
			}

			$this->logger->error(
				'Failed to import content',
				array(
					'entry_id' => $entry['id'],
					'error'    => $e->getMessage(),
				)
			);

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wordpress_status' => 'failed',
					'wordpress_error'  => $e->getMessage(),
				)
			);

			// Return error but don't throw — allow other entries to process.
			return new \WP_Error( 'import_failed', $e->getMessage() );
		}
	}

	/**
	 * Sync WordPress publish status back to Wisewand for analytics and tracking.
	 *
	 * Updates the is_published field on the Wisewand article via the REST API.
	 * Logs warnings on failure but never throws — a sync failure must not block
	 * the import from being recorded as successful.
	 *
	 * @since 2.8.2
	 *
	 * @param string $article_id   Wisewand article UUID.
	 * @param bool   $is_published True if published, false if draft/unpublished.
	 */
	private function update_wisewand_published_status( $article_id, $is_published ) {
		if ( ! $this->wisewand_api ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'WISEWAND_SYNC: No API client available, skipping is_published update' );
			}
			return;
		}

		try {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'WISEWAND_SYNC: Updating article ' . $article_id . ' with is_published = ' . ( $is_published ? 'true' : 'false' ) );
			}

			$result = $this->wisewand_api->update_article(
				$article_id,
				array( 'is_published' => $is_published )
			);

			if ( is_wp_error( $result ) ) {
				$this->logger->warning(
					'Failed to update Wisewand is_published status',
					array(
						'article_id'   => $article_id,
						'is_published' => $is_published,
						'error'        => $result->get_error_message(),
					)
				);
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'WISEWAND_SYNC: PATCH failed - ' . $result->get_error_message() );
				}
				return;
			}

			$this->logger->info(
				'Updated Wisewand is_published status',
				array(
					'article_id'   => $article_id,
					'is_published' => $is_published,
				)
			);

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'WISEWAND_SYNC: Successfully updated article ' . $article_id . ' is_published = ' . ( $is_published ? 'true' : 'false' ) );
			}
		} catch ( \Exception $e ) {
			$this->logger->error(
				'Exception updating Wisewand is_published status',
				array(
					'article_id'   => $article_id,
					'is_published' => $is_published,
					'error'        => $e->getMessage(),
				)
			);
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'WISEWAND_SYNC: Exception - ' . $e->getMessage() );
			}
		}
	}

	/**
	 * Update an existing WordPress post with regenerated content.
	 *
	 * Supports three merge strategies: 'replace' (default), 'append', and
	 * 'prepend'. Reads the existing post when appending or prepending.
	 * Throws on empty content and on wp_update_post() failure.
	 *
	 * @since 2.8.2
	 *
	 * @param array $entry   Calendar entry row (must include existing_post_id and content_merge_strategy).
	 * @param array $content Content array with 'title' and 'content' keys.
	 * @return int The post ID on success.
	 * @throws \Exception When content is empty or wp_update_post() fails.
	 */
	public function update_existing_post( $entry, $content ) {
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'UPDATE_POST: Starting update for post ID ' . $entry['existing_post_id'] );
			error_log( 'UPDATE_POST: Content keys available: ' . implode( ', ', array_keys( $content ) ) );
			error_log( 'UPDATE_POST: Content title: ' . ( $content['title'] ?? 'MISSING' ) );
			error_log( 'UPDATE_POST: Content content length: ' . ( isset( $content['content'] ) ? strlen( $content['content'] ) : 0 ) );
		}

		if ( empty( $content['content'] ) && empty( $content['title'] ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'UPDATE_POST: Both content and title are empty!' );
			}
			throw new \Exception( 'Content, title, and excerpt are empty.' );
		}

		$default_status = $this->options->get( 'wisewand_default_post_status', 'draft' );
		$post_data      = array(
			'ID'          => $entry['existing_post_id'],
			'post_status' => $default_status,
		);

		$strategy = $entry['content_merge_strategy'] ?? 'replace';
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'UPDATE_POST: Merge strategy: ' . $strategy );
		}

		if ( 'replace' === $strategy ) {
			$post_data['post_content'] = $content['content'] ?? '';
			$post_data['post_title']   = $content['title'] ?? '';
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'UPDATE_POST: Replace mode - Title: "' . $post_data['post_title'] . '"' );
				error_log( 'UPDATE_POST: Replace mode - Content length: ' . strlen( $post_data['post_content'] ) );
			}
		} elseif ( 'append' === $strategy ) {
			$existing                  = get_post( $entry['existing_post_id'] );
			$post_data['post_content'] = $existing->post_content . "\n\n" . ( $content['content'] ?? '' );
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'UPDATE_POST: Append mode - keeping existing title' );
			}
		} elseif ( 'prepend' === $strategy ) {
			$existing                  = get_post( $entry['existing_post_id'] );
			$post_data['post_content'] = ( $content['content'] ?? '' ) . "\n\n" . $existing->post_content;
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'UPDATE_POST: Prepend mode - keeping existing title' );
			}
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'UPDATE_POST: Final post_data keys: ' . implode( ', ', array_keys( $post_data ) ) );
			error_log( 'UPDATE_POST: Calling wp_update_post...' );
		}

		$result = wp_update_post( $post_data, true );

		if ( is_wp_error( $result ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'UPDATE_POST: wp_update_post returned error: ' . $result->get_error_message() );
			}
			throw new \Exception( esc_html( $result->get_error_message() ) );
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'UPDATE_POST: Post updated successfully, ID: ' . $result );
		}

		return $entry['existing_post_id'];
	}
}
