<?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\Utils\Rate_Limiter;

// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log -- error_log() is only used within WP_DEBUG conditionals for debugging

/**
 * CRON Retry Handler
 *
 * Phase 4 of the CRON pipeline: retries calendar entries that previously
 * failed generation, applying exponential backoff between attempts.
 * Entries without an article ID are re-queued for generation; entries that
 * already have an article ID are promoted back to 'generating' so the status
 * check phase picks them up without duplicating the API call.
 *
 * @package Wisewand
 * @since 2.8.2
 */
class CRON_Retry_Handler {

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

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

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

	/**
	 * Rate limiter for API calls.
	 *
	 * @var Rate_Limiter
	 */
	private $rate_limiter;

	/**
	 * Article generator (Phase 1) — used to create new API articles on retry.
	 *
	 * @var CRON_Article_Generator
	 */
	private $generator;

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

	/**
	 * Constructor.
	 *
	 * @since 2.8.2
	 *
	 * @param Options                $options       Options wrapper.
	 * @param Logger                 $logger        Logger instance.
	 * @param mixed                  $entry_manager Calendar entry manager.
	 * @param Rate_Limiter           $rate_limiter  Rate limiter instance.
	 * @param CRON_Article_Generator $generator     Article generator for re-queuing entries.
	 */
	public function __construct(
		Options $options,
		Logger $logger,
		$entry_manager,
		Rate_Limiter $rate_limiter,
		CRON_Article_Generator $generator
	) {
		$this->options       = $options;
		$this->logger        = $logger;
		$this->entry_manager = $entry_manager;
		$this->rate_limiter  = $rate_limiter;
		$this->generator     = $generator;
	}

	/**
	 * Set shared run summary by reference.
	 *
	 * @since 2.8.2
	 *
	 * @param array $summary Reference to the facade's run_summary array.
	 */
	public function set_run_summary( array &$summary ) {
		$this->run_summary = &$summary;
	}

	/**
	 * Step 4: Retry failed calendar entries with exponential backoff.
	 *
	 * Checks each 'failed' entry against its retry counter and the time elapsed
	 * since the last attempt. Entries without an article_id are sent back through
	 * the generator. Entries that already have an article_id are promoted to
	 * 'generating' so the status-check phase handles them without re-creating.
	 *
	 * When $force_retry is true (manual trigger), the exponential backoff window
	 * and max-retry limit are both bypassed and retry counters are reset to zero
	 * so every failed entry receives a fresh attempt.
	 *
	 * POLICY: up to 5 retries max with exponential backoff.
	 * Retry schedule: 0h, 2h, 4h, 8h, 16h = ~30 hours total before permanent failure.
	 *
	 * @since 2.8.2
	 *
	 * @param bool $force_retry When true, bypasses backoff window and max retries.
	 */
	public function step_4_retry_failed( $force_retry = false ) {
		$max_retries = $this->options->get( 'wisewand_cron_max_retries', 5 );

		$failed = $this->entry_manager->get_entries(
			array(
				'wisewand_status' => 'failed',
			)
		);

		if ( $force_retry ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 4: FORCE RETRY MODE - resetting retry counters and ignoring backoff for ' . count( $failed ) . ' failed entries' );
			}

			// Reset retry counters when manually triggered (give failed entries a fresh start).
			foreach ( $failed as $entry ) {
				if ( ! empty( $entry['wisewand_article_id'] ) ) {
					$this->entry_manager->update_entry(
						$entry['id'],
						array(
							'wisewand_retry_count' => 0,
							'wisewand_error'       => null,
						)
					);

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'CRON Step 4: Reset retry counter for entry ' . $entry['id'] );
					}
				}
			}

			// Reload entries after reset.
			$failed = $this->entry_manager->get_entries(
				array(
					'wisewand_status' => 'failed',
				)
			);
		} elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 4: Found ' . count( $failed ) . ' failed entries to check for retry (with exponential backoff)' );
		}

		$retried = 0;
		$skipped = 0;

		foreach ( $failed as $entry ) {
			$retry_count = $entry['wisewand_retry_count'] ?? 0;

			// In force retry mode, ignore max retries limit (user explicitly requesting retry).
			if ( ! $force_retry && $retry_count >= $max_retries ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 4: Skipping entry ' . $entry['id'] . ' - max retries reached (' . $retry_count . '/' . $max_retries . ')' );
				}

				// Mark as permanently failed — needs Wisewand support.
				$this->entry_manager->update_entry(
					$entry['id'],
					array(
						'wisewand_error' => $entry['wisewand_error'] . ' | Max retries reached (' . $max_retries . '). Please contact Wisewand support at https://app.wisewand.ai',
						'notes'          => 'PERMANENTLY FAILED after ' . $max_retries . ' automatic retries. The issue persists on Wisewand API side. User should contact Wisewand support for assistance.',
					)
				);

				$this->logger->warning(
					'Entry permanently failed - needs Wisewand support',
					array(
						'entry_id'    => $entry['id'],
						'keyword'     => $entry['keyword'],
						'retry_count' => $retry_count,
						'error'       => $entry['wisewand_error'],
					)
				);

				++$skipped;
				continue;
			}

			// Calculate whether the exponential backoff window has elapsed.
			$wait_hours       = pow( 2, $retry_count );
			$next_retry_time  = strtotime( $entry['updated_at'] ) + ( $wait_hours * 3600 );
			$time_until_retry = $next_retry_time - time();
			$should_retry     = time() >= $next_retry_time;

			// FORCE RETRY: ignore exponential backoff if manually triggered.
			if ( $force_retry ) {
				$should_retry = true;

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 4: FORCE retrying entry ' . $entry['id'] . ' - ' . $entry['keyword'] . ' (ignoring ' . ceil( $time_until_retry / 3600 ) . 'h wait)' );
				}
			}

			if ( $should_retry ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 4: Retrying entry ' . $entry['id'] . ' - ' . $entry['keyword'] . ' (attempt ' . ( $retry_count + 1 ) . '/' . $max_retries . ')' );
				}

				$this->logger->info(
					'Retrying failed entry',
					array(
						'entry_id'    => $entry['id'],
						'retry_count' => $retry_count + 1,
					)
				);

				if ( ! empty( $entry['wisewand_article_id'] ) ) {
					// Article already created — promote to 'generating' so status check handles it.
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'CRON Step 4: Entry ' . $entry['id'] . ' already has article_id (' . $entry['wisewand_article_id'] . '), checking status instead of recreating' );
					}

					$this->entry_manager->update_entry(
						$entry['id'],
						array(
							'wisewand_status' => 'generating',
							'wisewand_error'  => null,
						)
					);

					// Status check CRON will pick this up — do not recreate.
					++$skipped;
				} else {
					// No article_id — safe to create a new article.
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'CRON Step 4: Entry ' . $entry['id'] . ' has no article_id, creating new article' );
					}

					$this->entry_manager->update_entry(
						$entry['id'],
						array(
							'wisewand_status' => 'not_created',
							'wisewand_error'  => null,
						)
					);

					$this->generator->generate_article( $entry );
					++$retried;
					++$this->run_summary['retried'];

					$this->rate_limiter->smart_delay( 'wisewand_api', 1.0 );
				}
			} else {
				$hours_remaining = ceil( $time_until_retry / 3600 );

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 4: Skipping entry ' . $entry['id'] . ' - must wait ' . $hours_remaining . 'h before retry (exponential backoff)' );
				}

				++$skipped;
			}
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 4: Retried ' . $retried . ', Skipped ' . $skipped . ' (waiting for backoff time)' );
		}

		if ( $retried > 0 ) {
			$this->logger->info( 'Step 4: Retried failed entries', array( 'count' => $retried ) );
		}
	}
}
