<?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

class RSS_Article_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)
		$this->repository = $repository ?: new \Wisewand\Repositories\RSS_Repository();
	}

	/**
	 * Discover and track new article from feed
	 * Creates record if doesn't exist, updates if changed
	 *
	 * @param array  $wisewand_article_data Article data from Wisewand API
	 * @param int    $feed_id Local feed ID
	 * @param string $feed_wisewand_id Wisewand feed ID
	 * @return int|WP_Error Article tracking ID or error
	 */
	public function discover_article( $wisewand_article_data, $feed_id, $feed_wisewand_id ) {
		$wisewand_article_id = $wisewand_article_data['id'] ?? '';
		if ( empty( $wisewand_article_id ) ) {
			return new \WP_Error( 'invalid_article', __( 'Article ID missing', 'wisewand-ai-content-writer-seo-content-generator' ) );
		}

		// Check if article already tracked
		$existing = $this->get_article_by_wisewand_id( $wisewand_article_id );

		$article_data = array(
			'feed_id'               => $feed_id,
			'feed_wisewand_id'      => sanitize_text_field( $feed_wisewand_id ),
			'wisewand_article_id'   => sanitize_text_field( $wisewand_article_id ),
			'article_title'         => sanitize_text_field( $wisewand_article_data['title'] ?? '' ),
			'article_subject'       => sanitize_textarea_field( $wisewand_article_data['subject'] ?? '' ),
			'article_status'        => $this->normalize_article_status(
				$wisewand_article_data['status'] ?? 'unknown'
			),
			'article_created_at'    => ! empty( $wisewand_article_data['created_at'] ) ? gmdate( 'Y-m-d H:i:s', strtotime( $wisewand_article_data['created_at'] ) ) : null,
			'article_updated_at'    => ! empty( $wisewand_article_data['updated_at'] ) ? gmdate( 'Y-m-d H:i:s', strtotime( $wisewand_article_data['updated_at'] ) ) : null,
			'wisewand_is_published' => ! empty( $wisewand_article_data['is_published'] ) ? 1 : 0,
			'has_cover_image'       => ! empty( $wisewand_article_data['cover_image'] ) ? 1 : 0,
			'last_output_id'        => ! empty( $wisewand_article_data['last_output_id'] )
				? sanitize_text_field( $wisewand_article_data['last_output_id'] )
				: null,
			'updated_at'            => current_time( 'mysql' ),
		);

		// Debug logging for article status from API.
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			$this->logger->debug(
				'RSS_ARTICLE_STATUS: Article discovered with status from API',
				array(
					'wisewand_article_id' => $wisewand_article_id,
					'title'               => $wisewand_article_data['title'] ?? 'Unknown',
					'api_status_raw'      => $wisewand_article_data['status'] ?? 'MISSING',
					'saved_status'        => $article_data['article_status'],
				)
			);
		}

		if ( $existing ) {
			// Update existing article metadata
			$this->repository->update_article( $existing['id'], $article_data );

			return $existing['id'];
		} else {
			// Create new tracking record
			$article_data['discovered_at'] = current_time( 'mysql' );
			$article_data['import_status'] = 'pending';

			$article_id = $this->repository->create_article( $article_data );

			if ( $article_id ) {
				$this->logger->info(
					'New article discovered from feed',
					array(
						'article_id'          => $article_id,
						'wisewand_article_id' => $wisewand_article_id,
						'title'               => $article_data['article_title'],
						'feed_id'             => $feed_id,
						'article_status'      => $article_data['article_status'],
					)
				);

				return $article_id;
			}

			return new \WP_Error( 'insert_failed', __( 'Failed to track article', 'wisewand-ai-content-writer-seo-content-generator' ) );
		}
	}

	/**
	 * Check if article exists in tracking database and return detailed status
	 *
	 * @since 1.0.11
	 * @param string $wisewand_article_id The Wisewand article ID.
	 * @return array|false Array with status info if exists, false if not found.
	 *                     Array keys: import_status, wordpress_post_id, article_status
	 */
	public function check_article_exists( $wisewand_article_id ) {
		$article = $this->repository->check_article_exists( $wisewand_article_id );
		return $article ? $article : false;
	}

	/**
	 * Get article by Wisewand ID
	 *
	 * @param string $wisewand_article_id Wisewand article ID
	 * @return array|null Article data or null
	 */
	public function get_article_by_wisewand_id( $wisewand_article_id ) {
		$article = $this->repository->get_article_by_uuid( $wisewand_article_id );
		return $article ? (array) $article : null;
	}

	/**
	 * Get cached last_output_id for discoverarticle
	 *
	 * Used during import to skip redundant API calls.
	 * If cached output_id exists, we can use it directly instead of
	 * making an additional discoverarticle_has_output() API call.
	 *
	 * @since 1.2.0
	 * @param string $wisewand_article_id Article ID.
	 * @return string|null Cached output ID or null.
	 */
	public function get_cached_output_id( $wisewand_article_id ) {
		return $this->repository->get_cached_output_id( $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 ) {
		$article = $this->repository->get_article_by_wordpress_post_id( $post_id );
		return $article ? (array) $article : null;
	}

	/**
	 * Get article by internal tracking ID.
	 *
	 * @since 1.0.0
	 *
	 * @param int $article_id Article tracking ID.
	 * @return array|null Article data or null if not found.
	 */
	public function get_article( $article_id ) {
		$article = $this->repository->get_article( $article_id );
		return $article ? (array) $article : null;
	}

	/**
	 * Get pending articles for a feed (ready to import)
	 *
	 * @param int $feed_id Feed ID
	 * @param int $limit Maximum articles to return
	 * @return array Articles pending import
	 */
	public function get_pending_articles_for_feed( $feed_id, $limit = 10 ) {
		return $this->repository->get_pending_articles_for_feed( $feed_id, $limit );
	}

	/**
	 * Get articles for a feed with optional filters
	 *
	 * Performance optimization: Added LIMIT clause to prevent memory exhaustion
	 * with large datasets. Default limit is 1000 articles per feed.
	 *
	 * @param int   $feed_id Feed ID
	 * @param array $filters Optional filters (supports 'limit' parameter).
	 * @return array Articles
	 */
	public function get_feed_articles( $feed_id, $filters = array() ) {
		return $this->repository->get_feed_articles( $feed_id, $filters );
	}

	/**
	 * Mark article as importing
	 *
	 * @param int $article_id Article tracking ID
	 * @return bool Success
	 */
	public function mark_as_importing( $article_id ) {
		$result = $this->repository->mark_as_importing( $article_id );
		return ( $result !== false );
	}

	/**
	 * Mark article as successfully imported
	 *
	 * @param int    $article_id Article tracking ID
	 * @param int    $wordpress_post_id WordPress post ID
	 * @param string $wordpress_post_status WordPress post status
	 * @return bool Success
	 */
	public function mark_as_imported( $article_id, $wordpress_post_id, $wordpress_post_status = 'draft' ) {
		$result = $this->repository->mark_as_imported( $article_id, $wordpress_post_id, $wordpress_post_status );

		if ( $result !== false ) {
			$this->logger->info(
				'Article marked as imported',
				array(
					'article_id'        => $article_id,
					'wordpress_post_id' => $wordpress_post_id,
					'post_status'       => $wordpress_post_status,
				)
			);
		}

		return ( $result !== false );
	}

	/**
	 * Mark article as failed with error message
	 *
	 * @param int    $article_id Article tracking ID
	 * @param string $error_message Error message
	 * @return bool Success
	 */
	public function mark_as_failed( $article_id, $error_message ) {
		$result = $this->repository->mark_as_failed( $article_id, $error_message );

		if ( $result !== false ) {
			$this->logger->warning(
				'Article import failed',
				array(
					'article_id' => $article_id,
					'error'      => $error_message,
				)
			);
		}

		return ( $result !== false );
	}

	/**
	 * Mark article as deleted from Wisewand
	 *
	 * Used when Wisewand returns 404 indicating the discoverarticle
	 * no longer exists on their platform.
	 *
	 * @param int    $article_id Article tracking ID
	 * @param string $reason Optional reason for deletion
	 * @return bool Success
	 */
	public function mark_as_deleted( $article_id, $reason = '' ) {
		$reason = $reason ?: __( 'Article no longer exists in Wisewand (404)', 'wisewand-ai-content-writer-seo-content-generator' );
		$result = $this->repository->mark_as_deleted( $article_id, $reason );

		if ( $result !== false ) {
			$this->logger->info(
				'Article marked as deleted from Wisewand',
				array(
					'article_id' => $article_id,
					'reason'     => $reason,
				)
			);
		}

		return ( $result !== false );
	}

	/**
	 * Mark article as skipped
	 *
	 * @param int $article_id Article tracking ID
	 * @return bool Success
	 */
	public function mark_as_skipped( $article_id ) {
		$result = $this->repository->mark_as_skipped( $article_id );
		return ( $result !== false );
	}

	/**
	 * Get statistics for a feed
	 *
	 * @param int $feed_id Feed ID
	 * @return array Statistics
	 */
	public function get_feed_statistics( $feed_id ) {
		return $this->repository->get_feed_statistics( $feed_id );
	}

	/**
	 * Delete article tracking record
	 *
	 * @param int $article_id Article ID
	 * @return bool Success
	 */
	public function delete_article( $article_id ) {
		$result = $this->repository->delete_article( $article_id );
		return (bool) $result;
	}

	/**
	 * Clean up old failed and deleted articles
	 *
	 * Removes:
	 * - Failed articles older than 30 days (temporary errors likely resolved)
	 * - Deleted articles older than 7 days (404s, no longer in Wisewand)
	 * - Ungenerated discoverarticles older than 30 days (edge case cleanup)
	 *
	 * @since 1.0.13
	 * @return array Cleanup statistics.
	 */
	public function cleanup_old_articles() {
		$stats = $this->repository->cleanup_old_articles();

		// Log cleanup results.
		if ( $stats['failed_deleted'] > 0 || $stats['deleted_removed'] > 0 || $stats['ungenerated_removed'] > 0 ) {
			$this->logger->info(
				'Cleaned up old RSS articles',
				array(
					'failed_articles_removed'      => $stats['failed_deleted'],
					'deleted_articles_removed'     => $stats['deleted_removed'],
					'ungenerated_articles_removed' => $stats['ungenerated_removed'],
				)
			);
		}

		return $stats;
	}

	/**
	 * Permanently delete an article record from database
	 *
	 * Used to immediately remove 404-deleted articles to prevent retry loops.
	 *
	 * @since 1.0.15
	 * @param int    $article_id Article tracking ID.
	 * @param string $reason Reason for deletion (for logging).
	 * @return bool Success.
	 */
	public function delete_article_permanently( $article_id, $reason = '' ) {
		// Get article details before deletion for logging.
		$article = $this->get_article( $article_id );

		if ( ! $article ) {
			$this->logger->warning(
				'Attempted to delete non-existent article',
				array( 'article_id' => $article_id )
			);
			return false;
		}

		// Permanently delete from database.
		$result = $this->repository->delete_article( $article_id );

		if ( $result ) {
			$this->logger->info(
				'Article permanently deleted from database',
				array(
					'article_id'          => $article_id,
					'wisewand_article_id' => $article['wisewand_article_id'],
					'article_title'       => $article['article_title'],
					'reason'              => $reason ?: 'No reason provided',
					'was_imported'        => ! empty( $article['wordpress_post_id'] ),
					'wordpress_post_id'   => $article['wordpress_post_id'] ?? null,
				)
			);

			return true;
		}

		$this->logger->error(
			'Failed to permanently delete article',
			array(
				'article_id' => $article_id,
			)
		);

		return false;
	}

	/**
	 * Purge all articles marked as 'deleted' from database
	 *
	 * Removes articles that returned 404 from Wisewand API.
	 * Used for cleanup operations and preventing retry loops.
	 *
	 * @since 1.0.15
	 * @param int|null $feed_id Optional feed ID to limit purge scope.
	 * @return array Purge statistics.
	 */
	public function purge_deleted_articles( $feed_id = null ) {
		$stats = array(
			'deleted_count' => 0,
		);

		$deleted = $this->repository->purge_deleted_articles( $feed_id );
		$stats['deleted_count'] = (int) $deleted;

		if ( $deleted > 0 ) {
			$this->logger->info(
				'Purged deleted articles from database',
				array(
					'feed_id'          => $feed_id,
					'articles_removed' => $stats['deleted_count'],
				)
			);
		}

		return $stats;
	}

	/**
	 * Reset article to pending for retry
	 *
	 * @param int $article_id Article ID
	 * @return bool Success
	 */
	public function reset_to_pending( $article_id ) {
		$result = $this->repository->reset_to_pending( $article_id );
		return ( $result !== false );
	}

	/**
	 * Get recent articles for feed (for UI display)
	 *
	 * @param int $feed_id Feed ID
	 * @param int $limit Number of articles
	 * @return array Recent articles
	 */
	public function get_recent_articles( $feed_id, $limit = 5 ) {
		return $this->repository->get_recent_articles( $feed_id, $limit );
	}

	/**
	 * Get articles with pagination for table display
	 *
	 * @param int   $feed_id Feed ID
	 * @param int   $page Page number (1-indexed)
	 * @param int   $per_page Articles per page
	 * @param array $filters Optional filters
	 * @return array Paginated articles with total count
	 */
	public function get_articles_paginated( $feed_id, $page = 1, $per_page = 20, $filters = array() ) {
		global $wpdb;

		$offset = ( $page - 1 ) * $per_page;

		$where  = array( 'feed_id = %d' );
		$values = array( $feed_id );

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

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

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

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

		// Get total count - Table name and WHERE clause are built from parameterized conditions.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- WHERE clause built from parameterized conditions, table name is safe
		$count_query = $wpdb->prepare(
			"SELECT COUNT(*) FROM {$this->table_name} WHERE {$where_clause}",
			$values
		);
		// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query constructed with prepared components
		$total       = (int) $wpdb->get_var( $count_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		// Get paginated results - Table name and WHERE clause are built from parameterized conditions.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- WHERE clause built from parameterized conditions, table name is safe
		$query = $wpdb->prepare(
			"SELECT * FROM {$this->table_name}
             WHERE {$where_clause}
             ORDER BY article_created_at DESC
             LIMIT %d OFFSET %d",
			array_merge( $values, array( $per_page, $offset ) )
		);

		// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query constructed with prepared components
		$articles = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		return array(
			'articles'    => $articles,
			'total'       => $total,
			'page'        => $page,
			'per_page'    => $per_page,
			'total_pages' => ceil( $total / $per_page ),
		);
	}

	/**
	 * Normalize article status from Wisewand API to expected values.
	 *
	 * This method maps API status values to database-friendly normalized values,
	 * ensuring consistent status values for import filtering.
	 *
	 * @since 1.0.0
	 *
	 * @param string $api_status Status value from Wisewand API.
	 * @return string Normalized status value ('success', 'ready', 'creating', 'error', or 'ready' as default).
	 */
	private function normalize_article_status( $api_status ) {
		// Map API status values to expected database values.
		$valid_statuses = array( 'success', 'ready', 'creating', 'error' );

		$normalized = strtolower( trim( $api_status ) );

		if ( in_array( $normalized, $valid_statuses, true ) ) {
			return $normalized;
		}

		// Default to 'ready' for unknown statuses (allow import).
		$this->logger->warning(
			'Unknown article_status from API, defaulting to ready',
			array(
				'api_status'        => $api_status,
				'normalized_status' => 'ready',
			)
		);

		return 'ready';
	}
}
