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

/**
 * RSS Repository Class
 *
 * @package Wisewand
 * @subpackage Wisewand/includes/Repositories
 *
 * phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- False positive from Plugin Checker
 * All queries use $wpdb->prepare() with proper placeholders. The checker incorrectly flags
 * the prepare() method calls themselves. Known PHPCS limitation.
 *
 * phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Repository pattern requirement
 * phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table, no object cache
 */

namespace Wisewand\Repositories;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class RSS_Repository
 *
 * Handles database interactions for RSS Feeds and Articles.
 */
class RSS_Repository extends Base_Repository {

	/**
	 * Articles table name.
	 *
	 * @var string
	 */
	protected $articles_table;

	/**
	 * Initialize the repository.
	 */
	public function __construct() {
		parent::__construct( 'wisewand_rss_feeds' );
		$this->articles_table = $this->wpdb->prefix . 'wisewand_rss_articles';
	}

	/**
	 * Get all active feeds (v2.3.0: alias for get_all_active_feeds for backward compatibility)
	 *
	 * @since 1.0.0
	 * @deprecated 2.3.0 Use get_all_active_feeds() instead
	 * @return array
	 */
	public function get_active_feeds() {
		return $this->get_all_active_feeds();
	}

	/**
	 * Get all active feeds (v2.3.0: simplified - no next_sync_at filtering)
	 *
	 * Returns ALL feeds where auto_import_enabled = 1, ignoring next_sync_at.
	 * Used by simplified CRON system that syncs all active feeds each run.
	 *
	 * @since 2.3.0
	 * @return array Array of feed data (empty if no project configured)
	 */
	public function get_all_active_feeds() {
		// STRICT PROJECT ENFORCEMENT (RSS v2.1)
		// Only sync feeds that match the current project ID.
		$options    = new \Wisewand\Utils\Options();
		// Robust retrieval: Check 'wisewand_default_project_id' (set by Admin Manager) first,
		// then legacy 'wisewand_project_id' as fallback.
		$project_id = $options->get( 'wisewand_default_project_id' );
		if ( empty( $project_id ) ) {
			$project_id = $options->get( 'wisewand_project_id' );
		}

		if ( empty( $project_id ) ) {
			// If no project ID is configured, we shouldn't sync anything to be safe.
			return array();
		}

		// v2.3.0: Get ALL active feeds (ignore next_sync_at)
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		return $this->wpdb->get_results(
			$this->wpdb->prepare(
				"SELECT * FROM %i WHERE auto_import_enabled = 1 AND wisewand_project_id = %s ORDER BY id ASC",
				$this->table_name,
				$project_id
			),
			ARRAY_A
		);
	}

	/**
	 * Get feed by ID.
	 *
	 * @param int $id Feed ID.
	 * @return object|null
	 */
	public function get_feed( $id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		return $this->wpdb->get_row(
			$this->wpdb->prepare(
				'SELECT * FROM %i WHERE id = %d',
				$this->table_name,
				$id
			),
			ARRAY_A
		);
	}

	/**
	 * Get feed by Wisewand Feed ID (UUID).
	 *
	 * @param string $uuid Wisewand Feed ID.
	 * @return object|null
	 */
	public function get_feed_by_uuid( $uuid ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		return $this->wpdb->get_row(
			$this->wpdb->prepare(
				'SELECT * FROM %i WHERE wisewand_feed_id = %s',
				$this->table_name,
				$uuid
			),
			ARRAY_A
		);
	}

	/**
	 * Create or update a feed.
	 *
	 * @param array $data Feed data.
	 * @return int|false Insert ID or affected rows.
	 */
	public function save_feed( $data ) {
		// If ID exists, update, else insert.
		if ( isset( $data['id'] ) ) {
			$id = $data['id'];
			unset( $data['id'] );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
			return $this->wpdb->update(
				$this->table_name,
				$data,
				array( 'id' => $id ),
				null,
				array( '%d' )
			);
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
			$result = $this->wpdb->insert(
				$this->table_name,
				$data
			);
			return $result ? $this->wpdb->insert_id : false;
		}
	}

	/**
	 * Get article by Wisewand Article ID.
	 *
	 * @param string $article_id Wisewand Article ID.
	 * @return object|null
	 */
	public function get_article_by_uuid( $article_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		return $this->wpdb->get_row(
			$this->wpdb->prepare(
				'SELECT * FROM %i WHERE wisewand_article_id = %s',
				$this->articles_table,
				$article_id
			),
			ARRAY_A
		);
	}

	/**
	 * Save (Insert) an article.
	 *
	 * @param array $data Article data.
	 * @return int|false Insert ID.
	 */
	public function create_article( $data ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		$result = $this->wpdb->insert(
			$this->articles_table,
			$data
		);
		return $result ? $this->wpdb->insert_id : false;
	}

	/**
	 * Update an article.
	 *
	 * @param int   $id   Article ID.
	 * @param array $data Data to update.
	 * @return int|false Affected rows.
	 */
	public function update_article( $id, $data ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		return $this->wpdb->update(
			$this->articles_table,
			$data,
			array( 'id' => $id ),
			null,
			array( '%d' )
		);
	}

	/**
	 * Get articles by status for a feed
	 *
	 * @since 2.2.0
	 * @param int    $feed_id Feed ID.
	 * @param string $status  Article status.
	 * @param int    $limit   Max articles.
	 * @return array Articles.
	 */
	public function get_articles_by_status( $feed_id, $status, $limit = 10 ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		return $this->wpdb->get_results(
			$this->wpdb->prepare(
				'SELECT * FROM %i WHERE feed_id = %d AND status = %s ORDER BY discovered_at DESC LIMIT %d',
				$this->articles_table,
				$feed_id,
				$status,
				$limit
			),
			ARRAY_A
		);
	}

	/**
	 * Check if article exists by URL or GUID
	 *
	 * @since 2.2.0
	 * @param int    $feed_id Feed ID.
	 * @param string $url     Article URL.
	 * @param string $guid    Article GUID.
	 * @return bool True if exists.
	 */
	public function article_exists( $feed_id, $url, $guid ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		$result = $this->wpdb->get_var(
			$this->wpdb->prepare(
				'SELECT COUNT(*) FROM %i WHERE feed_id = %d AND (article_url = %s OR article_guid = %s)',
				$this->articles_table,
				$feed_id,
				$url,
				$guid
			)
		);

		return $result > 0;
	}

	/**
	 * Insert new article
	 *
	 * @since 2.2.0
	 * @param array $article_data Article data.
	 * @return int|WP_Error Article ID or error.
	 */
	public function insert_article( $article_data ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query required.
		$result = $this->wpdb->insert(
			$this->articles_table,
			$article_data
		);

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

		return $this->wpdb->insert_id;
	}

	/**
	 * Get all feeds with optional filters.
	 *
	 * @param array $filters Optional filters.
	 * @return array Array of feeds.
	 */
	public function get_all_feeds( $filters = array() ) {
		$where  = array( '1=1' );
		$values = array();

		if ( isset( $filters['auto_import_enabled'] ) ) {
			$where[]  = 'auto_import_enabled = %d';
			$values[] = $filters['auto_import_enabled'] ? 1 : 0;
		}

		if ( ! empty( $filters['wisewand_project_id'] ) ) {
			$where[]  = 'wisewand_project_id = %s';
			$values[] = $filters['wisewand_project_id'];
		}

		if ( ! empty( $filters['search'] ) ) {
			$where[]  = 'feed_name LIKE %s';
			$values[] = '%' . $this->wpdb->esc_like( $filters['search'] ) . '%';
		}

		$limit = isset( $filters['limit'] ) ? absint( $filters['limit'] ) : 1000;

		$where_clause = implode( ' AND ', $where );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_results(
			$this->wpdb->prepare(
				"SELECT * FROM %i WHERE {$where_clause} ORDER BY created_at DESC LIMIT %d",
				array_merge( array( $this->table_name ), $values, array( $limit ) )
			),
			ARRAY_A
		);
	}

	/**
	 * Update feed settings.
	 *
	 * @param int   $feed_id  Feed ID.
	 * @param array $settings Settings to update.
	 * @return int|false Affected rows.
	 */
	public function update_feed_settings( $feed_id, $settings ) {
		$settings['updated_at'] = current_time( 'mysql' );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->update(
			$this->table_name,
			$settings,
			array( 'id' => $feed_id )
		);
	}

	/**
	 * Update feed statistics.
	 *
	 * @param int   $feed_id Feed ID.
	 * @param array $stats   Statistics to update.
	 * @return int|false Affected rows.
	 */
	public function update_feed_statistics( $feed_id, $stats ) {
		$stats['updated_at'] = current_time( 'mysql' );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->update(
			$this->table_name,
			$stats,
			array( 'id' => $feed_id )
		);
	}

	/**
	 * Get feeds due for synchronization.
	 *
	 * @param int $limit Maximum number of feeds.
	 * @return array Feeds due for sync.
	 */
	public function get_feeds_due_for_sync( $limit = 100 ) {
		$now = current_time( 'mysql' );

		// STRICT PROJECT ENFORCEMENT (RSS v2.1)
		// Only sync feeds that match the current project ID.
		$options    = new \Wisewand\Utils\Options();
		// Robust retrieval: Check 'wisewand_default_project_id' first, then legacy 'wisewand_project_id'
		$project_id = $options->get( 'wisewand_default_project_id' );
		if ( empty( $project_id ) ) {
			$project_id = $options->get( 'wisewand_project_id' );
		}

		if ( empty( $project_id ) ) {
			return array();
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_results(
			$this->wpdb->prepare(
				'SELECT * FROM %i
				 WHERE auto_import_enabled = 1
				 AND (next_sync_at IS NULL OR next_sync_at <= %s)
				 AND wisewand_project_id = %s
				 ORDER BY next_sync_at ASC
				 LIMIT %d',
				$this->table_name,
				$now,
				$project_id,
				$limit
			),
			ARRAY_A
		);
	}

	/**
	 * Update next sync time.
	 *
	 * @param int    $feed_id   Feed ID.
	 * @param string $next_sync Next sync timestamp (mysql format).
	 * @return int|false Affected rows.
	 */
	public function update_next_sync_time( $feed_id, $next_sync ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->update(
			$this->table_name,
			array(
				'next_sync_at' => $next_sync,
				'updated_at'   => current_time( 'mysql' ),
			),
			array( 'id' => $feed_id ),
			array( '%s', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Delete a feed.
	 *
	 * @param int $feed_id Feed ID.
	 * @return int|false Number of rows deleted.
	 */
	public function delete_feed( $feed_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->delete(
			$this->table_name,
			array( 'id' => $feed_id ),
			array( '%d' )
		);
	}

	/**
	 * Get total feeds count.
	 *
	 * @param array $filters Optional filters.
	 * @return int Count.
	 */
	public function get_feeds_count( $filters = array() ) {
		$where  = array( '1=1' );
		$values = array();

		if ( isset( $filters['auto_import_enabled'] ) ) {
			$where[]  = 'auto_import_enabled = %d';
			$values[] = $filters['auto_import_enabled'] ? 1 : 0;
		}

		$where_clause = implode( ' AND ', $where );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return (int) $this->wpdb->get_var(
			$this->wpdb->prepare(
				"SELECT COUNT(*) FROM %i WHERE {$where_clause}",
				array_merge( array( $this->table_name ), $values )
			)
		);
	}

	/**
	 * Check if article exists and return status.
	 *
	 * @param string $wisewand_article_id Wisewand Article ID.
	 * @return array|false Article data or false.
	 */
	public function check_article_exists( $wisewand_article_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_row(
			$this->wpdb->prepare(
				'SELECT import_status, wordpress_post_id
				 FROM %i
				 WHERE wisewand_article_id = %s
				 LIMIT 1',
				$this->articles_table,
				$wisewand_article_id
			),
			ARRAY_A
		);
	}

	/**
	 * Get cached output ID.
	 *
	 * @param string $wisewand_article_id Wisewand Article ID.
	 * @return string|null Output ID or null.
	 */
	public function get_cached_output_id( $wisewand_article_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_var(
			$this->wpdb->prepare(
				'SELECT last_output_id FROM %i WHERE wisewand_article_id = %s',
				$this->articles_table,
				$wisewand_article_id
			)
		);
	}

	/**
	 * Get article by WordPress Post ID.
	 *
	 * @param int $post_id WordPress Post ID.
	 * @return array|null Article data or null.
	 */
	public function get_article_by_wordpress_post_id( $post_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_row(
			$this->wpdb->prepare(
				'SELECT * FROM %i WHERE wordpress_post_id = %d',
				$this->articles_table,
				$post_id
			),
			ARRAY_A
		);
	}

	/**
	 * Get article by local ID.
	 *
	 * @param int $article_id Article ID.
	 * @return array|null Article data or null.
	 */
	public function get_article( $article_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_row(
			$this->wpdb->prepare(
				'SELECT * FROM %i WHERE id = %d',
				$this->articles_table,
				$article_id
			),
			ARRAY_A
		);
	}

	/**
	 * Get pending articles for a feed.
	 *
	 * @param int $feed_id Feed ID.
	 * @param int $limit   Limit.
	 * @return array Articles.
	 */
	public function get_pending_articles_for_feed( $feed_id, $limit = 10 ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_results(
			$this->wpdb->prepare(
				"SELECT * FROM %i
				 WHERE feed_id = %d
				 AND import_status NOT IN ('imported', 'deleted', 'skipped')
				 ORDER BY discovered_at ASC
				 LIMIT %d",
				$this->articles_table,
				$feed_id,
				$limit
			),
			ARRAY_A
		);
	}

	/**
	 * Get articles for a feed with filters.
	 *
	 * @param int   $feed_id Feed ID.
	 * @param array $filters Filters.
	 * @return array Articles.
	 */
	public function get_feed_articles( $feed_id, $filters = array() ) {
		$where  = array( 'feed_id = %d' );
		$values = array( $feed_id );

		if ( ! empty( $filters['import_status'] ) ) {
			$where[]  = 'import_status = %s';
			$values[] = $filters['import_status'];
		}

		$limit = isset( $filters['limit'] ) ? absint( $filters['limit'] ) : 1000;

		$where_clause = implode( ' AND ', $where );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_results(
			$this->wpdb->prepare(
				"SELECT * FROM %i WHERE {$where_clause} ORDER BY discovered_at DESC LIMIT %d",
				array_merge( array( $this->articles_table ), $values, array( $limit ) )
			),
			ARRAY_A
		);
	}

	/**
	 * Mark article as importing.
	 *
	 * @param int $article_id Article ID.
	 * @return int|false Affected rows.
	 */
	public function mark_as_importing( $article_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->update(
			$this->articles_table,
			array(
				'import_status'          => 'importing',
				'last_import_attempt_at' => current_time( 'mysql' ),
				'updated_at'             => current_time( 'mysql' ),
			),
			array( 'id' => $article_id ),
			array( '%s', '%s', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Mark article as imported.
	 *
	 * @param int    $article_id            Article ID.
	 * @param int    $wordpress_post_id     Post ID.
	 * @param string $wordpress_post_status Post status.
	 * @return int|false Affected rows.
	 */
	public function mark_as_imported( $article_id, $wordpress_post_id, $wordpress_post_status = 'draft' ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->update(
			$this->articles_table,
			array(
				'import_status'         => 'imported',
				'wordpress_post_id'     => $wordpress_post_id,
				'wordpress_post_status' => $wordpress_post_status,
				'imported_at'           => current_time( 'mysql' ),
				'import_error'          => null,
				'updated_at'            => current_time( 'mysql' ),
			),
			array( 'id' => $article_id ),
			array( '%s', '%d', '%s', '%s', '%s', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Mark article as failed.
	 *
	 * @param int    $article_id    Article ID.
	 * @param string $error_message Error message.
	 * @return int|false Affected rows.
	 */
	public function mark_as_failed( $article_id, $error_message ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $this->wpdb->update(
			$this->articles_table,
			array(
				'import_status'          => 'failed',
				'import_error'           => $error_message,
				'last_import_attempt_at' => current_time( 'mysql' ),
				'updated_at'             => current_time( 'mysql' ),
			),
			array( 'id' => $article_id ),
			array( '%s', '%s', '%s', '%s' ),
			array( '%d' )
		);

		if ( $result !== false ) {
			// Increment import_attempts manually
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$this->wpdb->query(
				$this->wpdb->prepare(
					'UPDATE %i SET import_attempts = import_attempts + 1 WHERE id = %d',
					$this->articles_table,
					$article_id
				)
			);
		}

		return $result;
	}

	/**
	 * Mark article as deleted.
	 *
	 * @param int    $article_id Article ID.
	 * @param string $reason     Reason.
	 * @return int|false Affected rows.
	 */
	public function mark_as_deleted( $article_id, $reason = '' ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->update(
			$this->articles_table,
			array(
				'import_status'          => 'deleted',
				'import_error'           => $reason,
				'last_import_attempt_at' => current_time( 'mysql' ),
				'updated_at'             => current_time( 'mysql' ),
			),
			array( 'id' => $article_id ),
			array( '%s', '%s', '%s', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Mark article as skipped.
	 *
	 * @param int $article_id Article ID.
	 * @return int|false Affected rows.
	 */
	public function mark_as_skipped( $article_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->update(
			$this->articles_table,
			array(
				'import_status' => 'skipped',
				'updated_at'    => current_time( 'mysql' ),
			),
			array( 'id' => $article_id ),
			array( '%s', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Get feed statistics.
	 *
	 * @param int $feed_id Feed ID.
	 * @return array Statistics.
	 */
	public function get_feed_statistics( $feed_id ) {
		$stats = array(
			'total'     => 0,
			'pending'   => 0,
			'importing' => 0,
			'imported'  => 0,
			'failed'    => 0,
			'skipped'   => 0,
		);

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$import_counts = $this->wpdb->get_results(
			$this->wpdb->prepare(
				'SELECT import_status, COUNT(*) as count
				 FROM %i
				 WHERE feed_id = %d
				 GROUP BY import_status',
				$this->articles_table,
				$feed_id
			),
			ARRAY_A
		);

		foreach ( $import_counts as $row ) {
			$status           = $row['import_status'];
			$count            = (int) $row['count'];
			$stats[ $status ] = $count;
			$stats['total']  += $count;
		}

		return $stats;
	}

	/**
	 * Delete article.
	 *
	 * @param int $article_id Article ID.
	 * @return int|false Number of rows deleted.
	 */
	public function delete_article( $article_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->delete(
			$this->articles_table,
			array( 'id' => $article_id ),
			array( '%d' )
		);
	}

	/**
	 * Cleanup old articles.
	 *
	 * @return array Cleanup stats.
	 */
	public function cleanup_old_articles() {
		$stats = array(
			'failed_deleted'      => 0,
			'deleted_removed'     => 0,
			'ungenerated_removed' => 0,
		);

		$failed_cutoff = gmdate( 'Y-m-d H:i:s', strtotime( '-30 days' ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$failed_deleted          = $this->wpdb->query(
			$this->wpdb->prepare(
				"DELETE FROM %i
				 WHERE import_status = 'failed'
				 AND last_import_attempt_at < %s",
				$this->articles_table,
				$failed_cutoff
			)
		);
		$stats['failed_deleted'] = (int) $failed_deleted;

		$deleted_cutoff = gmdate( 'Y-m-d H:i:s', strtotime( '-7 days' ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$deleted_removed          = $this->wpdb->query(
			$this->wpdb->prepare(
				"DELETE FROM %i
				 WHERE import_status = 'deleted'
				 AND last_import_attempt_at < %s",
				$this->articles_table,
				$deleted_cutoff
			)
		);
		$stats['deleted_removed'] = (int) $deleted_removed;

		$ungenerated_cutoff = gmdate( 'Y-m-d H:i:s', strtotime( '-30 days' ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$ungenerated_removed          = $this->wpdb->query(
			$this->wpdb->prepare(
				"DELETE FROM %i
				 WHERE import_status = 'failed'
				 AND import_error LIKE %s
				 AND discovered_at < %s",
				$this->articles_table,
				'%no generated output yet%',
				$ungenerated_cutoff
			)
		);
		$stats['ungenerated_removed'] = (int) $ungenerated_removed;

		return $stats;
	}

	/**
	 * Purge deleted articles.
	 *
	 * @param int|null $feed_id Feed ID.
	 * @return int Number of rows deleted.
	 */
	public function purge_deleted_articles( $feed_id = null ) {
		if ( $feed_id ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			return $this->wpdb->query(
				$this->wpdb->prepare(
					"DELETE FROM %i WHERE import_status = 'deleted' AND feed_id = %d",
					$this->articles_table,
					$feed_id
				)
			);
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			return $this->wpdb->query(
				$this->wpdb->prepare(
					"DELETE FROM %i WHERE import_status = 'deleted'",
					$this->articles_table
				)
			);
		}
	}

	/**
	 * Reset article to pending.
	 *
	 * @param int $article_id Article ID.
	 * @return int|false Affected rows.
	 */
	public function reset_to_pending( $article_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->update(
			$this->articles_table,
			array(
				'import_status' => 'pending',
				'import_error'  => null,
				'updated_at'    => current_time( 'mysql' ),
			),
			array( 'id' => $article_id ),
			array( '%s', '%s', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Get recent articles for feed.
	 *
	 * @param int $feed_id Feed ID.
	 * @param int $limit   Limit.
	 * @return array Articles.
	 */
	public function get_recent_articles( $feed_id, $limit = 5 ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $this->wpdb->get_results(
			$this->wpdb->prepare(
				'SELECT * FROM %i
				 WHERE feed_id = %d
				 ORDER BY discovered_at DESC
				 LIMIT %d',
				$this->articles_table,
				$feed_id,
				$limit
			),
			ARRAY_A
		);
	}

	/**
	 * Get feed schedule settings
	 *
	 * Retrieves schedule_mode and schedule_time for a feed.
	 * Used to determine if feed has custom scheduling.
	 *
	 * @since 2.1.3
	 * @param int $feed_id Feed ID.
	 * @return array|null {
	 *     Schedule settings or null if feed not found.
	 *
	 *     @type string      $schedule_mode Schedule mode ('inherit' or 'custom').
	 *     @type string|null $schedule_time Schedule time (HH:MM:SS) or null.
	 * }
	 */
	public function get_feed_schedule( $feed_id ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $this->wpdb->get_row(
			$this->wpdb->prepare(
				'SELECT schedule_mode, schedule_time FROM %i WHERE id = %d',
				$this->table_name,
				$feed_id
			),
			ARRAY_A
		);

		return $result;
	}

	/**
	 * Update feed schedule settings
	 *
	 * Updates schedule_mode and schedule_time for a feed.
	 * Validates inputs before saving.
	 *
	 * @since 2.1.3
	 * @param int         $feed_id       Feed ID.
	 * @param string      $schedule_mode Schedule mode ('inherit' or 'custom').
	 * @param string|null $schedule_time Schedule time (HH:MM:SS format) or null.
	 * @return bool True on success, false on failure.
	 */
	public function update_feed_schedule( $feed_id, $schedule_mode, $schedule_time ) {
		// Validate schedule_mode
		if ( ! \Wisewand\Utils\Schedule_Validator::validate_schedule_mode( $schedule_mode ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( sprintf(
					'Wisewand RSS Repository: Invalid schedule_mode "%s" for feed #%d',
					$schedule_mode,
					$feed_id
				) );
			}
			return false;
		}

		// If mode is 'custom', require and validate schedule_time
		if ( 'custom' === $schedule_mode ) {
			if ( empty( $schedule_time ) ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( sprintf(
						'Wisewand RSS Repository: schedule_time required for custom mode (feed #%d)',
						$feed_id
					) );
				}
				return false;
			}

			// Validate time format
			if ( ! \Wisewand\Utils\Schedule_Validator::validate_time_format( $schedule_time ) ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( sprintf(
						'Wisewand RSS Repository: Invalid schedule_time "%s" for feed #%d',
						$schedule_time,
						$feed_id
					) );
				}
				return false;
			}

			// Normalize to HH:MM:SS
			$schedule_time = \Wisewand\Utils\Schedule_Validator::normalize_time( $schedule_time );
		} else {
			// If mode is 'inherit', clear schedule_time
			$schedule_time = null;
		}

		// Prepare update data
		$update_data = array(
			'schedule_mode' => $schedule_mode,
			'schedule_time' => $schedule_time,
			'updated_at'    => current_time( 'mysql' ),
		);

		$format = array( '%s', '%s', '%s' );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $this->wpdb->update(
			$this->table_name,
			$update_data,
			array( 'id' => $feed_id ),
			$format,
			array( '%d' )
		);

		if ( false !== $result ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( sprintf(
					'Wisewand RSS Repository: Updated schedule for feed #%d (mode: %s, time: %s)',
					$feed_id,
					$schedule_mode,
					$schedule_time ? $schedule_time : 'null'
				) );
			}
			return true;
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( sprintf(
				'Wisewand RSS Repository: Failed to update schedule for feed #%d: %s',
				$feed_id,
				$this->wpdb->last_error
			) );
		}

		return false;
	}
}
