<?php
/**
 * This file is part of the Wisewand WordPress plugin.
 *
 * @package Wisewand
 * @since 1.0.0
 * @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\RSS;

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

use Wisewand\Logging\Logger;

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Direct database queries required for custom plugin tables with dynamic conditions
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log -- error_log() is only used within WP_DEBUG conditionals for debugging

class RSS_Feed_Manager {

	private $logger;
	private $repository;

	public function __construct( Logger $logger, ?\Wisewand\Repositories\RSS_Repository $repository = null ) {
		$this->logger = $logger;
		// Backwards compatibility: if repository not passed, create it (or handle legacy)
		// Ideally, we enforce injection, but for safety during refactor:
		$this->repository = $repository ?: new \Wisewand\Repositories\RSS_Repository();
	}

	/**
	 * Sync feed data from Wisewand API to local database
	 * Creates new feed or updates existing
	 *
	 * @param array $wisewand_feed_data Feed data from Wisewand API
	 * @return int|WP_Error Feed ID or error
	 */
	public function sync_feed_from_wisewand( $wisewand_feed_data ) {
		$wisewand_feed_id = $wisewand_feed_data['id'] ?? '';
		if ( empty( $wisewand_feed_id ) ) {
			return new \WP_Error( 'invalid_feed', __( 'Feed ID missing from Wisewand data', 'wisewand-ai-content-writer-seo-content-generator' ) );
		}

		// Check if feed exists
		$existing = $this->get_feed_by_wisewand_id( $wisewand_feed_id );

		// Determine feed_type from entity_type with validation
		$feed_type = 'articles'; // Safe default
		if ( isset( $wisewand_feed_data['entity_type'] ) ) {
			$entity_type = sanitize_text_field( $wisewand_feed_data['entity_type'] );
			// Validate against allowed types
			$valid_types = array( 'articles', 'discoverarticles' );
			if ( in_array( $entity_type, $valid_types, true ) ) {
				$feed_type = $entity_type;
			} else {
				// Log warning for unknown entity_type
				$this->logger->warning(
					'Unknown entity_type encountered, defaulting to articles',
					array(
						'entity_type'      => $entity_type,
						'wisewand_feed_id' => $wisewand_feed_id,
						'feed_name'        => $wisewand_feed_data['title'] ?? 'unknown',
					)
				);
			}
		} else {
			// Log info when entity_type is missing
			$this->logger->info(
				'Missing entity_type in feed data, defaulting to articles',
				array(
					'wisewand_feed_id' => $wisewand_feed_id,
					'feed_name'        => $wisewand_feed_data['title'] ?? 'unknown',
				)
			);
		}

		$feed_data = array(
			'wisewand_feed_id'    => sanitize_text_field( $wisewand_feed_id ),
			'feed_type'           => $feed_type,
			'feed_name'           => sanitize_text_field( $wisewand_feed_data['title'] ?? '' ),
			'feed_url'            => esc_url_raw( $wisewand_feed_data['url'] ?? '' ),
			'source_url'          => esc_url_raw( $wisewand_feed_data['url'] ?? '' ),
			'feed_icon'           => ! empty( $wisewand_feed_data['icon'] ) ? esc_url_raw( $wisewand_feed_data['icon'] ) : null,
			'feed_color'          => ! empty( $wisewand_feed_data['color'] ) ? sanitize_hex_color( $wisewand_feed_data['color'] ) : null,
			'wisewand_status'     => $wisewand_feed_data['is_active'] ? 'active' : 'inactive',
			'wisewand_frequency'  => sanitize_text_field( $wisewand_feed_data['frequency'] ?? '24h' ),
			'wisewand_limit'      => absint( $wisewand_feed_data['limit'] ?? 1 ),
			'wisewand_cost'       => floatval( $wisewand_feed_data['cost'] ?? 2 ),
			'wisewand_project_id' => ! empty( $wisewand_feed_data['config']['project_id'] ) ? sanitize_text_field( $wisewand_feed_data['config']['project_id'] ) : null,
			'last_fetched_at'     => ! empty( $wisewand_feed_data['last_fetched_at'] ) ? gmdate( 'Y-m-d H:i:s', strtotime( $wisewand_feed_data['last_fetched_at'] ) ) : null,
			'updated_at'          => current_time( 'mysql' ),
		);

		if ( $existing ) {
			// Update existing feed
			$result = $this->repository->save_feed( array_merge( array( 'id' => $existing['id'] ), $feed_data ) );

			if ( false === $result ) {
				return new \WP_Error( 'update_failed', __( 'Failed to update feed', 'wisewand-ai-content-writer-seo-content-generator' ) );
			}

			$this->logger->info(
				'Feed synced from Wisewand (updated)',
				array(
					'feed_id'          => $existing['id'],
					'wisewand_feed_id' => $wisewand_feed_id,
					'feed_name'        => $feed_data['feed_name'],
				)
			);

			return $existing['id'];
		} else {
			// Create new feed
			$feed_data['created_at'] = current_time( 'mysql' );
			$feed_data['created_by'] = get_current_user_id();

			$feed_id = $this->repository->save_feed( $feed_data );

			if ( ! $feed_id ) {
				return new \WP_Error( 'insert_failed', __( 'Failed to create feed', 'wisewand-ai-content-writer-seo-content-generator' ) );
			}

			$this->logger->info(
				'Feed synced from Wisewand (created)',
				array(
					'feed_id'          => $feed_id,
					'wisewand_feed_id' => $wisewand_feed_id,
					'feed_name'        => $feed_data['feed_name'],
				)
			);

			return $feed_id;
		}
	}

	/**
	 * Get feed by Wisewand ID
	 *
	 * @param string $wisewand_feed_id Wisewand feed ID
	 * @return array|null Feed data or null
	 */
	public function get_feed_by_wisewand_id( $wisewand_feed_id ) {
		$feed = $this->repository->get_feed_by_uuid( $wisewand_feed_id );
		return $feed ? (array) $feed : null;
	}

	/**
	 * Get feed by local ID
	 *
	 * @param int $feed_id Local feed ID
	 * @return array|null Feed data or null
	 */
	public function get_feed( $feed_id ) {
		$feed = $this->repository->get_feed( $feed_id );
		$feed = $feed ? (array) $feed : null;

		// v1.3.0: Type cast integer fields (MySQL returns as strings with ARRAY_A)
		// This fixes JavaScript conditionals treating string "0" as truthy
		if ( $feed ) {
			$feed['id']                              = (int) $feed['id'];
			$feed['auto_import_enabled']             = (int) $feed['auto_import_enabled'];
			$feed['use_ai_category_matching']        = (int) $feed['use_ai_category_matching'];
			$feed['use_custom_publication_schedule'] = (int) $feed['use_custom_publication_schedule'];
			$feed['max_articles_per_sync']           = (int) $feed['max_articles_per_sync'];
			$feed['custom_interval_hours']           = (int) $feed['custom_interval_hours'];
			$feed['target_author']                   = (int) $feed['target_author'];
			$feed['total_articles']                  = (int) $feed['total_articles'];
			$feed['articles_imported']               = (int) $feed['articles_imported'];
			$feed['articles_pending']                = (int) $feed['articles_pending'];
			$feed['articles_failed']                 = (int) $feed['articles_failed'];
			$feed['use_global_settings']             = (int) ( $feed['use_global_settings'] ?? 1 );

			// v1.9.4: Fix Feed Settings Hierarchy
			// Instead of blanket overwrite based on 'use_global_settings', we now use per-field inheritance.
			// This allows 'Hybrid' configuration (e.g. specific schedule but global author).

			$options = new \Wisewand\Utils\Options();

			// 1. Publish Mode: Inherit if explicitly set to 'global' (Default in DB)
			if ( $feed['publish_mode'] === 'global' ) {
				$feed['publish_mode'] = $options->get( 'wisewand_rss_default_publish_mode', 'draft' );
			}

			// 2. Auto Import: Inherit if -1 (Inherit Token)
			// Legacy use_global_settings override REMOVED (RSS v2.2.7)
			// Per-field inherit_* flags are now authoritative.
			if ( (int) $feed['auto_import_enabled'] === -1 ) {
				$feed['auto_import_enabled'] = $options->get( 'wisewand_rss_default_auto_import', true ) ? 1 : 0;
			}

			// 3. Max Articles: Inherit if -1
			if ( (int) $feed['max_articles_per_sync'] === -1 ) {
				$feed['max_articles_per_sync'] = (int) $options->get( 'wisewand_rss_default_max_articles', 5 );
			}

			// 4. Target Author: Inherit if 0 (System/Unassigned treated as Global Default)
			if ( (int) $feed['target_author'] === 0 ) {
				$default_author = (int) $options->get( 'wisewand_rss_default_target_author', 0 );
				if ( $default_author > 0 ) {
					$feed['target_author'] = $default_author;
				}
			}

			// 5. AI Category Matching: Inherit if -1
			if ( (int) $feed['use_ai_category_matching'] === -1 ) {
				$feed['use_ai_category_matching'] = $options->get( 'wisewand_rss_default_ai_category_matching', false ) ? 1 : 0;
			} else {
				// Keep DB value (0 or 1)
			}
			
			// 6. Import Frequency/Schedule:
			// Previously overwritten by global if use_global_settings=1.
			// Now we only use DB values.
			// If we implement 'global' for frequency later, we add check here.
			// For now, we respect the specific schedule in the DB.

			// Apply global daily time ONLY if we were inheriting frequency (which we aren't yet fully)
			// But let's look at the user's issue: "Feed setup on publish daily".
			// If frequency is daily, we should trust the DB time.

			// v2.2.0: Apply unified inheritance system
			$feed = $this->apply_global_inheritance( $feed );
		}

		return $feed ?: null;
	}

	/**
	 * Apply global defaults based on inheritance flags
	 *
	 * @since 2.2.0
	 * @param array $feed Feed data from database.
	 * @return array Feed with inherited values applied.
	 */
	private function apply_global_inheritance( $feed ) {
		$options = new \Wisewand\Utils\Options();

		// Import Frequency
		if ( ! empty( $feed['inherit_import_frequency'] ) && 1 === (int) $feed['inherit_import_frequency'] ) {
			$feed['import_schedule_type'] = $options->get(
				'wisewand_rss_default_import_frequency',
				'every_5_minutes'
			);
		}

		// Max Articles Per Sync
		if ( ! empty( $feed['inherit_max_articles'] ) && 1 === (int) $feed['inherit_max_articles'] ) {
			$feed['max_articles_per_sync'] = (int) $options->get(
				'wisewand_rss_default_max_articles',
				5
			);
		}

		// Publish Mode
		if ( ! empty( $feed['inherit_publish_mode'] ) && 1 === (int) $feed['inherit_publish_mode'] ) {
			$feed['publish_mode']  = $options->get( 'wisewand_rss_publish_mode', 'immediate' );
			$feed['schedule_time'] = $options->get( 'wisewand_rss_publish_time', '14:00' );
		}

		// Target Author
		if ( ! empty( $feed['inherit_author'] ) && 1 === (int) $feed['inherit_author'] ) {
			$feed['target_author'] = (int) $options->get( 'wisewand_rss_default_target_author', 1 );
		}

		// AI Category Matching
		if ( ! empty( $feed['inherit_ai_categories'] ) && 1 === (int) $feed['inherit_ai_categories'] ) {
			$feed['use_ai_category_matching'] = $options->get(
				'wisewand_rss_default_ai_category_matching',
				false
			) ? 1 : 0;
		}

		// Edge case handling: Check for deleted primary feed or circular inheritance
		// For v2.2.0, we don't have primary feed references yet, so this is future-proofing
		if ( ! empty( $feed['primary_feed_id'] ) ) {
			$primary_feed = $this->get_feed( $feed['primary_feed_id'] );
			if ( ! $primary_feed ) {
				// Primary feed deleted - log warning and use global defaults
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log(
						sprintf(
							'Wisewand RSS: Feed %d references deleted primary feed %d, using global defaults',
							$feed['id'],
							$feed['primary_feed_id']
						)
					);
				}
				// Clear invalid reference
				$feed['primary_feed_id'] = null;
			}
		}

		return $feed;
	}

	/**
	 * Get all feeds with optional filters
	 *
	 * Performance optimization: Added LIMIT clause to prevent memory exhaustion
	 * with large datasets. Default limit is 1000 feeds (more than enough for
	 * typical WordPress installations).
	 *
	 * @param array $filters Optional filters (supports 'limit' parameter).
	 * @return array Array of feeds
	 */
	public function get_all_feeds( $filters = array() ) {
		$feeds = $this->repository->get_all_feeds( $filters );

		// v1.3.0: Type cast integer fields for all feeds (same as get_feed)
		if ( $feeds ) {
			foreach ( $feeds as &$feed ) {
				$feed['id']                              = (int) $feed['id'];
				$feed['auto_import_enabled']             = (int) $feed['auto_import_enabled'];
				$feed['use_ai_category_matching']        = (int) $feed['use_ai_category_matching'];
				$feed['use_custom_publication_schedule'] = (int) $feed['use_custom_publication_schedule'];
				$feed['max_articles_per_sync']           = (int) $feed['max_articles_per_sync'];
				$feed['custom_interval_hours']           = (int) $feed['custom_interval_hours'];
				$feed['target_author']                   = (int) $feed['target_author'];
				$feed['total_articles']                  = (int) $feed['total_articles'];
				$feed['articles_imported']               = (int) $feed['articles_imported'];
				$feed['articles_pending']                = (int) $feed['articles_pending'];
				$feed['articles_failed']                 = (int) $feed['articles_failed'];
				$feed['use_global_settings']             = (int) ( $feed['use_global_settings'] ?? 1 );

				// v2.2.0: Apply unified inheritance system
				$feed = $this->apply_global_inheritance( $feed );
			}
		}

		return $feeds;
	}

	/**
	 * Get all active feeds (v2.3.0 - simplified: no next_sync_at filtering)
	 *
	 * Returns all feeds where auto_import_enabled = 1, delegating to repository.
	 * Used by simplified CRON system to sync ALL active feeds on global schedule.
	 *
	 * @since 2.3.0
	 * @return array Array of feed data with type casting and global inheritance applied
	 */
	public function get_all_active_feeds() {
		$feeds = $this->repository->get_all_active_feeds();

		// Apply same type casting as get_all_feeds()
		if ( $feeds ) {
			foreach ( $feeds as &$feed ) {
				$feed['id']                              = (int) $feed['id'];
				$feed['auto_import_enabled']             = (int) $feed['auto_import_enabled'];
				$feed['use_ai_category_matching']        = (int) $feed['use_ai_category_matching'];
				$feed['use_custom_publication_schedule'] = (int) $feed['use_custom_publication_schedule'];
				$feed['max_articles_per_sync']           = (int) $feed['max_articles_per_sync'];
				$feed['custom_interval_hours']           = (int) $feed['custom_interval_hours'];
				$feed['target_author']                   = (int) $feed['target_author'];
				$feed['total_articles']                  = (int) $feed['total_articles'];
				$feed['articles_imported']               = (int) $feed['articles_imported'];
				$feed['articles_pending']                = (int) $feed['articles_pending'];
				$feed['articles_failed']                 = (int) $feed['articles_failed'];
				$feed['use_global_settings']             = (int) ( $feed['use_global_settings'] ?? 1 );

				// v2.2.0: Apply unified inheritance system
				$feed = $this->apply_global_inheritance( $feed );
			}
		}

		return $feeds;
	}

	/**
	 * Update feed settings (automation and import configuration)
	 *
	 * @param int   $feed_id Feed ID
	 * @param array $settings Settings to update
	 * @return bool|WP_Error Success or error
	 */
	public function update_feed_settings( $feed_id, $settings ) {
		global $wpdb;

		// VERSION MARKER: v2.1-fixed (2025-01-03) - This ensures new code is loaded
		$version = 'v2.1-fixed';

		$allowed_fields = array(
			'enabled_for_site', // RSS v2.0: Feed activation status (legacy)
			'sync_status',      // RSS v2.0: Actual status field in database
			'auto_import_enabled',
			'import_frequency',
			'import_schedule_type',
			'import_schedule_time',
			'import_schedule_days',
			'max_articles_per_sync',
			'import_status_filter',
			'target_post_status',
			'publish_mode',                    // RSS v2.1: Publication mode (schedule, publish, draft, global)
			'target_author',
			'target_categories',
			'target_tags',
			'target_language',  // WPML: Target language for imported articles
			'use_ai_category_matching',
			'custom_interval_hours',           // RSS v2.0: Custom sync interval
			'use_custom_publication_schedule', // RSS v2.0: Publication control
			'publication_time_range',          // RSS v2.0: Time range preference
			'publication_allowed_days',        // RSS v2.0: Allowed days JSON
			'use_global_settings',             // RSS v2.1: Global settings flag
			'schedule_mode',                   // RSS v2.1.3: Schedule mode (inherit, custom)
			'schedule_time',                   // RSS v2.1.3: Custom schedule time (HH:MM:SS)
			'inherit_import_frequency',        // RSS v2.2: Inheritance flag for import frequency
			'inherit_author',                  // RSS v2.2: Inheritance flag for target author
			'inherit_categories',              // RSS v2.2: Inheritance flag for categories
			'inherit_publish_mode',            // RSS v2.2: Inheritance flag for publish mode
			'inherit_max_articles',            // RSS v2.2: Inheritance flag for max articles
			'inherit_ai_categories',           // RSS v2.2: Inheritance flag for AI categories
		);

		$update_data = array();
		$format      = array();

		foreach ( $settings as $key => $value ) {
			// Check if field is allowed
			$is_allowed = in_array( $key, $allowed_fields );

			if ( $is_allowed ) {
				$update_data[ $key ] = $value;

				// Determine format
				// v1.9.4: Allowed signed integers for inheritance tokens (-1)
				// v2.2.0: Added inheritance flags to integer fields
				if ( in_array( $key, array(
					'enabled_for_site',
					'auto_import_enabled',
					'use_ai_category_matching',
					'use_global_settings',
					'use_custom_publication_schedule',
					'target_author',
					'max_articles_per_sync',
					'custom_interval_hours',
					'inherit_import_frequency',
					'inherit_author',
					'inherit_categories',
					'inherit_publish_mode',
					'inherit_max_articles',
					'inherit_ai_categories'
				), true ) ) {
					$format[] = '%d';
				} elseif ( $key === 'sync_status' ) {
					$format[] = '%s';  // sync_status is a string field ('enabled'/'disabled')
				} else {
					$format[] = '%s';
				}
			}
		}

		if ( empty( $update_data ) ) {
			return new \WP_Error( 'no_data', __( 'No valid settings to update', 'wisewand-ai-content-writer-seo-content-generator' ) );
		}

		// RSS v2.1.3: Handle schedule fields separately via Repository method
		if ( isset( $settings['schedule_mode'] ) ) {
			$schedule_mode = sanitize_text_field( wp_unslash( $settings['schedule_mode'] ) );
			$schedule_time = isset( $settings['schedule_time'] )
				? sanitize_text_field( wp_unslash( $settings['schedule_time'] ) )
				: null;

			// Validate schedule_mode
			if ( ! \Wisewand\Utils\Schedule_Validator::validate_schedule_mode( $schedule_mode ) ) {
				return new \WP_Error(
					'invalid_schedule_mode',
					__( 'Invalid schedule mode. Must be "inherit" or "custom".', 'wisewand-ai-content-writer-seo-content-generator' )
				);
			}

			// If custom mode, validate time
			if ( 'custom' === $schedule_mode && $schedule_time ) {
				$schedule_time = \Wisewand\Utils\Schedule_Validator::normalize_time( $schedule_time );
				if ( ! \Wisewand\Utils\Schedule_Validator::validate_time_format( $schedule_time ) ) {
					return new \WP_Error(
						'invalid_time_format',
						__( 'Invalid time format. Use HH:MM (e.g., 14:30).', 'wisewand-ai-content-writer-seo-content-generator' )
					);
				}
			}

			// Save schedule via Repository (handles validation internally too)
			$schedule_result = $this->repository->update_feed_schedule( $feed_id, $schedule_mode, $schedule_time );

			if ( ! $schedule_result ) {
				return new \WP_Error(
					'schedule_update_failed',
					__( 'Failed to update feed schedule settings.', 'wisewand-ai-content-writer-seo-content-generator' )
				);
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( sprintf(
					'Wisewand RSS Feed Manager: Feed %d schedule updated: mode=%s, time=%s',
					$feed_id,
					$schedule_mode,
					$schedule_time ? $schedule_time : 'null'
				) );
			}

			// Remove from update_data (already handled)
			unset( $update_data['schedule_mode'] );
			unset( $update_data['schedule_time'] );
		}

		$update_data['updated_at'] = current_time( 'mysql' );
		$format[]                  = '%s';

		$result = $this->repository->update_feed_settings( $feed_id, $update_data );

		if ( false === $result ) {
			return new \WP_Error( 'update_failed', __( 'Failed to update feed settings', 'wisewand-ai-content-writer-seo-content-generator' ) );
		}

		$this->logger->info(
			array(
				'feed_id'  => $feed_id,
				'settings' => array_keys( $update_data ),
			)
		);

		// RSS v2.1 FIX: Recalculate next sync time immediately!
		// Previously, changing frequency didn't take effect until the OLD timer expired.
		// Now we assume if user changes settings, they want the new schedule to apply from NOW.
		if ( isset( $update_data['import_schedule_type'] ) || isset( $update_data['import_frequency'] ) || isset( $update_data['custom_interval_hours'] ) ) {
			// Pass update_data as overrides to prevent stale reads from DB (Race Condition Fix)
			$this->calculate_next_sync_time( $feed_id, false, $update_data );
			
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( "RSS_FEED: Settings updated, force-recalculated next_sync for feed {$feed_id}" );
			}
		}

		return true;
	}

	/**
	 * Update feed statistics
	 *
	 * @param int   $feed_id Feed ID
	 * @param array $stats Statistics to update
	 * @return bool Success
	 */
	public function update_feed_statistics( $feed_id, $stats ) {
		global $wpdb;

		$update_data = array();

		if ( isset( $stats['total_articles'] ) ) {
			$update_data['total_articles'] = absint( $stats['total_articles'] );
		}
		if ( isset( $stats['articles_success'] ) ) {
			$update_data['articles_success'] = absint( $stats['articles_success'] );
		}
		if ( isset( $stats['articles_error'] ) ) {
			$update_data['articles_error'] = absint( $stats['articles_error'] );
		}
		if ( isset( $stats['articles_imported'] ) ) {
			$update_data['articles_imported'] = absint( $stats['articles_imported'] );
		}
		if ( isset( $stats['articles_pending'] ) ) {
			$update_data['articles_pending'] = absint( $stats['articles_pending'] );
		}
		if ( isset( $stats['articles_failed'] ) ) {
			$update_data['articles_failed'] = absint( $stats['articles_failed'] );
		}
		if ( isset( $stats['last_sync_at'] ) ) {
			$update_data['last_sync_at'] = $stats['last_sync_at'];
		}
		if ( isset( $stats['last_import_at'] ) ) {
			$update_data['last_import_at'] = $stats['last_import_at'];
		}

		if ( empty( $update_data ) ) {
			return true;
		}

		$update_data['updated_at'] = current_time( 'mysql' );

		$this->repository->update_feed_statistics( $feed_id, $update_data );

		return true;
	}

	/**
	 * Get feeds due for synchronization
	 *
	 * Performance optimization: Added LIMIT clause to prevent processing
	 * too many feeds at once. Default limit is 100 feeds per sync cycle.
	 *
	 * @param int $limit Maximum number of feeds to return (default 100).
	 * @return array Feeds that need syncing
	 */
	public function get_feeds_due_for_sync( $limit = 100 ) {
		return $this->repository->get_feeds_due_for_sync( $limit );
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		return $feeds;
	}

	/**
	 * Calculate and set next sync time based on schedule settings
	 *
	 * @param int  $feed_id       Feed ID
	 * @param bool $allow_catchup If true, allows scheduling a past time (today) for immediate execution by Cron.
	 *                            Use this when settings change, but NOT after a successful sync.
	 * @return bool Success
	 */
	public function calculate_next_sync_time( $feed_id, $allow_catchup = false, $feed_data_override = array() ) {
		global $wpdb;

		$feed = $this->get_feed( $feed_id );
		if ( ! $feed ) {
			return false;
		}

		// RSS v2.1 FIX: Merge overrides to prevent stale DB reads (Race Condition)
		if ( ! empty( $feed_data_override ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( "RSS_DEBUG: Feed {$feed_id} applying overrides: " . wp_json_encode( $feed_data_override ) );
			}
			$feed = array_merge( $feed, $feed_data_override );
		}


		$schedule_type = ! empty( $feed['import_schedule_type'] ) ? $feed['import_schedule_type'] : 'every_6_hours';
		$schedule_time = $feed['import_schedule_time'];
		$schedule_days = $feed['import_schedule_days'];

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( sprintf( 'RSS_DEBUG: calculate_next_sync_time for Feed %d (Type: %s)', $feed_id, $schedule_type ) );
		}

		$next_sync = null;
		
		// Fix for Timezone Consistency (v2.0.4):
		// Using UTC (`time()`) for intervals caused mismatches because the Cron job compares 
		// `next_sync_at` against `current_time('mysql')` (Local Time).
		// We MUST use `current_time('timestamp')` (Local Timestamp) for ALL calculations so that 
		// `gmdate()` stores a string matching the Local Time.
		$now_local = current_time( 'timestamp' );

		switch ( $schedule_type ) {
			case 'every_5_minutes':
				// Next 5 minutes
				$next_sync = strtotime( '+5 minutes', $now_local );
				break;

			case 'every_30_minutes':
				// Next 30 minutes
				$next_sync = strtotime( '+30 minutes', $now_local );
				break;

			case 'hourly':
				// Next hour
				$next_sync = strtotime( '+1 hour', $now_local );
				break;

			case 'every_6_hours':
				// Next 6-hour interval
				$next_sync = strtotime( '+6 hours', $now_local );
				break;

			case 'daily':
				// Calculate next daily sync at specific time (requires Local Time context)
				if ( $schedule_time ) {
					// I must store "17:05:00" if verifying against Local Time.
					// OR I must change Query to compare against UTC.
					
					// Changing Query is safer but requires touching Repository.
					// Changing Storage to use LOCAL time timestamps is what the previous code did:
					// $now = current_time('timestamp'); // 17:00 local representation
					// $next = 17:05.
					// gmdate($next) -> "17:05:00".
					// DB: "17:05:00".
					// Query against "17:00:00". It waits 5 mins. This WORKS for Cron.
					
					// SO WHY does the User see "7h"?
					// Because the JS/Display logic compares `next_sync_at` (DB String) against `Date.now()` (Browser/UTC).
					// DB says "17:05". Browser says "10:00". Diff = 7 hours.
					// The System works (Cron runs correctly). The DISPLAY is wrong because the Display assumes UTC DB or handles TZ differently.
					
					// If I change to store UTC in DB ("10:05"), Cron (comparing to Local "17:00") will execute immediately (Wrong).
					// UNLESS I update Cron Query to use UTC.
					// `RSS_Repository::get_feeds_due_for_sync`: `$now = current_time( 'mysql' );` (Local).
					// If I change this to `$now = current_time( 'mysql', 1 );` (GMT), then I can store UTC in DB.
					
					// STRATEGY: 
					// 1. Switch DB to store UTC (Standard Practice).
					// 2. Update `RSS_Repository` to query using UTC.
					// 3. Update Display logic to handle UTC (it likely already expects UTC as per `Date.now` comparison).
					
					// BUT changing DB semantics is risky for existing data (Daily schedules already stored as Local).
					// Users' existing "Daily 09:00" is stored as "09:00:00".
					// If I switch to checking against UTC, "09:00:00" will be checked against UTC time.
					// If I am UTC+7. 09:00 Local is 02:00 UTC.
					// If I check at 02:00 UTC, "09:00" > "02:00", so it waits until 09:00 UTC (16:00 Local).
					// This shifts the schedule by the Offset.
					
					// OPTION B (Preserve Local Time in DB, Fix Display):
					// Keep storing Local Time in DB.
					// Update `RSS_Feed_Manager` to calculate relative intervals using Local Time (keep existing logic).
					// Update `Wisewand_RSS_Page.php` rendering logic to convert that Local Time String to a UTC Timestamp before sending to JS.
					
					// In `ajax_list_feeds`:
					// `$local_feed['next_sync_ts'] = strtotime( $local_feed['next_sync_at'] );`
					// `strtotime` assumes string is in Default TZ (UTC in WP).
					// But our string "17:05" is conceptually Local (UTC+7).
					// So `strtotime("17:05")` -> 17:05 UTC.
					// We want 17:05 Local -> 10:05 UTC.
					// We need to subtract the offset.
					
					// This seems safer: Fix the Display Translation.
					// The DB stores "Wall Clock Time" for the User's Site.
					// Cron checks against "Wall Clock Time".
					// Display needs to convert "Wall Clock Time" to "Real UTC" for countdown.
					
					// I will adopt OPTION B. Fix `Wisewand_RSS_Page.php` to adjust timestamp for display.
					
					// Reverting changes to RSS_Feed_Manager (using current_time is intentional for Local DB storage).
					
					$next_sync = $this->calculate_daily_sync_time( $schedule_time, $schedule_days, $now_local, $allow_catchup );
				} else {
					$next_sync = strtotime( '+1 day', $now_local );
				}
				break;

			case 'custom':
				// Use import_frequency field (e.g., "every_12_hours")
				$frequency = $feed['import_frequency'] ?? 'every_6_hours';
				if ( preg_match( '/every_(\d+)_hours/', $frequency, $matches ) ) {
					$hours     = absint( $matches[1] );
					$next_sync = strtotime( "+{$hours} hours", $now_utc );
				} else {
					$next_sync = strtotime( '+6 hours', $now_utc ); // Fallback
				}
				break;

			default:
				$next_sync = strtotime( '+6 hours', $now_utc );
		}

		$this->repository->update_next_sync_time( $feed_id, gmdate( 'Y-m-d H:i:s', $next_sync ) );
		
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( sprintf( 'RSS_DEBUG: Feed %d next sync set to %s (UTC)', $feed_id, gmdate( 'Y-m-d H:i:s', $next_sync ) ) );
		}

		return true;
	}

	/**
	 * Calculate next daily sync time respecting time and days settings
	 *
	 * @param string $schedule_time Time in H:i:s format
	 * @param string $schedule_days Comma-separated day numbers (1-7)
	 * @param int    $from_timestamp Starting timestamp
	 * @param bool   $allow_catchup  If true, checks if "Today" is a valid candidate even if passed.
	 * @return int Next sync timestamp
	 */
	private function calculate_daily_sync_time( $schedule_time, $schedule_days, $from_timestamp, $allow_catchup = false ) {
		$target_time = strtotime( $schedule_time );
		$hour        = (int) gmdate( 'H', $target_time );
		$minute      = (int) gmdate( 'i', $target_time );
		$second      = 0;

		$now = $from_timestamp;

		// Parse allowed days (1=Monday, 7=Sunday)
		$allowed_days = ! empty( $schedule_days ) ? array_map( 'intval', explode( ',', $schedule_days ) ) : array( 1, 2, 3, 4, 5, 6, 7 );

		// Start with TODAY at target time
		// Using mktime/gmmktime to construct today's target timestamp precisely
		// current_time('timestamp') is WP local time (if timezone set) so we construct relative to that?
		// Better: strtotime( 'today H:i:s' ) relative to WP timezone setup.
		// Since we passed $from_timestamp (typically 'now'), let's rely on string parsing relative to it.
		// NOTE: 'today 09:30' in strtotime uses current day.
		
		$candidate = strtotime( sprintf( 'today %02d:%02d:00', $hour, $minute ), $now );

		// Check if we missed it for today
		if ( $candidate < $now ) {
			if ( $allow_catchup ) {
				// We allow past time!
				// BUT we must verify if TODAY is an allowed day.
				$day_of_week = (int) gmdate( 'N', $candidate );
				if ( in_array( $day_of_week, $allowed_days, true ) ) {
					// It's today, it's allowed, and we caught it (late).
					// Return it (past value). CRON will see it as overdue and run immediately.
					return $candidate;
				}
			}
			
			// If not allowed catchup or today not allowed, move to tomorrow
			$candidate = strtotime( '+1 day', $candidate );
		}

		// Now find the next valid future day
		for ( $i = 0; $i < 14; $i++ ) {
			$day_of_week = (int) gmdate( 'N', $candidate ); // 1=Monday, 7=Sunday

			if ( in_array( $day_of_week, $allowed_days, true ) ) {
				return $candidate;
			}

			// Try next day
			$candidate = strtotime( '+1 day', $candidate );
		}

		// Fallback
		return strtotime( 'tomorrow ' . sprintf( '%02d:%02d:00', $hour, $minute ), $now );
	}

	/**
	 * Delete feed and all associated articles
	 *
	 * @param int $feed_id Feed ID
	 * @return bool Success
	 */
	public function delete_feed( $feed_id ) {
		// Foreign key constraint will auto-delete associated articles
		$result = $this->repository->delete_feed( $feed_id );

		if ( $result ) {
			$this->logger->info( 'Feed deleted', array( 'feed_id' => $feed_id ) );
		}

		return (bool) $result;
	}

	/**
	 * Get total feeds count with optional filters
	 *
	 * @param array $filters Optional filters
	 * @return int Count
	 */
	public function get_feeds_count( $filters = array() ) {
		return $this->repository->get_feeds_count( $filters );
	}

	/**
	 * Get global default publish mode
	 *
	 * Returns the site-wide default for how RSS articles should be published.
	 * Used when feed's publish_mode is set to 'global'.
	 *
	 * RSS v2.1.4: Now uses unified option `wisewand_rss_publish_mode` with mapping.
	 *
	 * @since 1.4.0
	 * @return string Default publish mode (schedule, publish, or draft).
	 */
	public function get_global_publish_mode() {
		$unified_mode = get_option( 'wisewand_rss_publish_mode', 'draft' );

		// Map new format to legacy format
		$mode_map = array(
			'immediate' => 'publish',
			'draft'     => 'draft',
			'scheduled' => 'schedule',
		);

		return $mode_map[ $unified_mode ] ?? 'draft';
	}

	/**
	 * Set global default publish mode
	 *
	 * Updates the site-wide default for RSS article publication behavior.
	 * Validates input against allowed modes.
	 *
	 * RSS v2.1.4: Now saves to unified option `wisewand_rss_publish_mode` with mapping.
	 *
	 * @since 1.4.0
	 * @param string $mode Publish mode (schedule, publish, or draft).
	 * @return bool True if mode was set, false if invalid mode provided.
	 */
	public function set_global_publish_mode( $mode ) {
		$allowed_modes = array( 'schedule', 'publish', 'draft' );

		if ( ! in_array( $mode, $allowed_modes, true ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log(
					sprintf(
						'Wisewand RSS: Invalid global publish mode "%s". Allowed: %s',
						$mode,
						implode( ', ', $allowed_modes )
					)
				);
			}
			return false;
		}

		// Map legacy format to new format
		$reverse_map = array(
			'publish'  => 'immediate',
			'draft'    => 'draft',
			'schedule' => 'scheduled',
		);

		$unified_mode = $reverse_map[ $mode ] ?? 'draft';
		$result       = update_option( 'wisewand_rss_publish_mode', $unified_mode );

		if ( $result && WP_DEBUG ) {
			error_log(
				sprintf(
					'Wisewand RSS: Global publish mode set to "%s" (unified: %s)',
					$mode,
					$unified_mode
				)
			);
		}

		return $result;
	}

	/**
	 * Recalculate next sync time for all feeds using global settings.
	 *
	 * Called when global default frequency is changed.
	 *
	 * @since 2.1.0
	 */
	public function recalculate_global_feeds_next_sync() {
		// Get all feeds (no filters).
		// Note: Pagination not currently supported in repository for this method,
		// assuming reasonable number of feeds.
		$all_feeds = $this->repository->get_all_feeds();

		$count = 0;
		foreach ( $all_feeds as $feed ) {
			// Check if feed uses global schedule (default).
			// If import_schedule_type is empty or explicitly 'global' (if that becomes an option).
			// currently, absence of specific override implies global or default behavior depending on logic.
			// simplify: just recalculate ALL enabled auto-import feeds.
			// The calculate_next_sync_time method is smart enough to respect individual overrides if present.
			// It will re-read the NEW global option if the feed relies on it.

			if ( empty( $feed['auto_import_enabled'] ) ) {
				continue;
			}

			// Force recalculation
			// Use catchup=true to ensure immediate sync if global frequency became tighter
			$this->calculate_next_sync_time( $feed['id'], true );
			$count++;
		}

		if ( $count > 0 ) {
			$this->logger->info(
				"Global settings changed: Recalculated next_sync for {$count} feeds",
				array( 'count' => $count )
			);
		}
	}
}
