<?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, WordPress.PHP.DevelopmentFunctions.error_log_print_r, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- error_log() and print_r() are only used within WP_DEBUG conditionals for debugging; table names derived from $wpdb->prefix are safe to interpolate

/**
 * CRON Status Checker
 *
 * Phase 2 of the CRON pipeline: polls the Wisewand API for the generation
 * status of articles in 'creating' or 'generating' state and transitions
 * them to 'ready' (or 'failed') once the API confirms completion.
 *
 * @package Wisewand
 * @since 2.8.2
 */
class CRON_Status_Checker {

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

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

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

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

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

	/**
	 * Endpoint resolver (from Generator, shared to avoid duplication).
	 *
	 * @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                  $wisewand_api  Wisewand API client or null.
	 * @param mixed                  $entry_manager Calendar entry manager.
	 * @param Rate_Limiter           $rate_limiter  Rate limiter instance.
	 * @param CRON_Article_Generator $generator     Generator (used for determine_endpoint).
	 */
	public function __construct( Options $options, Logger $logger, $wisewand_api, $entry_manager, Rate_Limiter $rate_limiter, CRON_Article_Generator $generator ) {
		$this->options       = $options;
		$this->logger        = $logger;
		$this->wisewand_api  = $wisewand_api;
		$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 2: Check generation status for all articles in 'creating'/'generating' state.
	 *
	 * Queries all calendar entries that have an article ID and are not yet
	 * confirmed complete, then polls the API for each one.
	 *
	 * @since 2.8.2
	 */
	public function step_2_check_generation_status() {
		global $wpdb;
		$table = $wpdb->prefix . 'wisewand_editorial_calendar';

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for generating articles
		$generating = $wpdb->get_results(
			"SELECT * FROM {$table}
			WHERE wisewand_status IN ('creating', 'generating')
			AND wisewand_article_id IS NOT NULL
			AND wisewand_article_id != ''
			ORDER BY id ASC",
			ARRAY_A
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 2: Found ' . count( $generating ) . ' entries to check (creating OR generating status)' );
		}

		$this->logger->info(
			'Step 2: Check generation status',
			array(
				'count'            => count( $generating ),
				'statuses_checked' => array( 'creating', 'generating' ),
			)
		);

		if ( empty( $generating ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 2: No entries found with creating/generating status and article_id' );
			}
			return;
		}

		foreach ( $generating as $entry ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 2: Checking entry ' . $entry['id'] . ' - ' . $entry['keyword'] . ' (status: ' . $entry['wisewand_status'] . ', article_id: ' . $entry['wisewand_article_id'] . ')' );
			}

			$this->check_article_status( $entry );
			++$this->run_summary['status_checked'];

			$this->rate_limiter->smart_delay( 'wisewand_api', 0.5 );
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 2: Completed checking ' . count( $generating ) . ' entries' );
		}
	}

	/**
	 * Check the generation status of a single article.
	 *
	 * Queries the API at the primary endpoint. On a 404, falls back to
	 * alternative endpoints and self-heals the database record if the article
	 * is found elsewhere. Updates entry status to 'ready', 'failed', or leaves
	 * it unchanged when still generating.
	 *
	 * @since 2.8.2
	 *
	 * @param array $entry Calendar entry row from the database.
	 */
	private function check_article_status( array $entry ) {
		if ( ! $this->wisewand_api ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON check_article_status: No Wisewand API client available' );
			}
			return;
		}

		if ( ! $entry['wisewand_article_id'] ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON check_article_status: Entry ' . $entry['id'] . ' has no wisewand_article_id' );
			}
			return;
		}

		try {
			$content_type = $this->generator->determine_endpoint( $entry['wisewand_type'] ?? 'blog' );

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log(
					sprintf(
						'CRON: Checking article status - ID: %s, Type: %s, Endpoint: %s',
						$entry['wisewand_article_id'],
						$entry['wisewand_type'] ?? 'blog',
						$content_type
					)
				);
			}

			$article = $this->wisewand_api->get_content( $content_type, $entry['wisewand_article_id'] );

			// 404 might mean wrong endpoint — try fallback.
			if ( is_wp_error( $article ) ) {
				$error_code    = $article->get_error_code();
				$error_message = $article->get_error_message();

				if ( 'wisewand_resource_not_found' === $error_code ||
					false !== strpos( $error_message, '404' ) ||
					false !== strpos( $error_message, 'not found' ) ) {

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log(
							sprintf(
								'CRON: Article not found at %s endpoint, trying fallback for ID: %s',
								$content_type,
								$entry['wisewand_article_id']
							)
						);
					}

					$fallback_result = $this->check_article_status_with_fallback( $entry, $content_type );

					if ( ! is_wp_error( $fallback_result ) ) {
						$article = $fallback_result;
					}
				}
			}

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

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '=== CRON STATUS CHECK ===' );
				error_log( 'Entry ID: ' . $entry['id'] );
				error_log( 'Keyword: ' . $entry['keyword'] );
				error_log( 'Article ID: ' . $entry['wisewand_article_id'] );
				error_log( 'Full API Response: ' . wp_json_encode( $article, JSON_PRETTY_PRINT ) );
				error_log( '=========================' );
			}

			$status = $article['status'] ?? $article['data']['status'] ?? 'unknown';

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON: Extracted status for entry ' . $entry['id'] . ': ' . $status );
			}

			$this->logger->debug(
				'Article status check',
				array(
					'entry_id'           => $entry['id'],
					'article_id'         => $entry['wisewand_article_id'],
					'status'             => $status,
					'full_response_keys' => array_keys( $article ),
				)
			);

			if ( 'success' === $status ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON: Article ready! Updating entry ' . $entry['id'] . ' (' . $entry['keyword'] . ') to READY status' );
				}

				$update_result = $this->entry_manager->update_entry(
					$entry['id'],
					array(
						'wisewand_status'         => 'ready',
						'wordpress_status'        => 'scheduled',
						'generation_completed_at' => current_time( 'mysql' ),
					)
				);

				if ( is_wp_error( $update_result ) ) {
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'CRON: Failed to update entry to ready: ' . $update_result->get_error_message() );
					}
				} elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON: Successfully updated entry ' . $entry['id'] . ' to READY status with wordpress_status=scheduled' );
				}

				$this->logger->info(
					'Article generation completed',
					array(
						'entry_id'   => $entry['id'],
						'article_id' => $entry['wisewand_article_id'],
						'keyword'    => $entry['keyword'],
					)
				);

			} elseif ( 'error' === $status ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'FULL WISEWAND API ERROR RESPONSE:' );
					error_log( wp_json_encode( $article, JSON_PRETTY_PRINT ) );
				}

				$error_msg = $article['error']
					?? $article['status_detail']
					?? $article['data']['error']
					?? $article['data']['error_message']
					?? $article['data']['status_detail']
					?? '[No error details provided by Wisewand API]';

				// Detect insufficient credits and provide actionable link.
				if ( false !== stripos( $error_msg, 'credit' ) || false !== stripos( $error_msg, 'insufficient' ) ) {
					$full_error = $error_msg . ' | Buy more credits: https://app.wisewand.ai/buy-credits';
					delete_transient( 'wisewand_credit_balance' );
				} else {
					$wisewand_url = 'https://app.wisewand.ai/articles/' . $entry['wisewand_article_id'];
					$full_error   = $error_msg . ' | Check Wisewand Dashboard: ' . $wisewand_url;
				}

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'ERROR MESSAGE: ' . $full_error );
				}

				throw new \Exception( $full_error );

			} elseif ( 'queued' === $status || 'running' === $status ) {
				// Still generating — do nothing until next CRON run.
				$this->logger->debug(
					'Article still generating',
					array(
						'entry_id' => $entry['id'],
						'status'   => $status,
					)
				);
			}

		} catch ( \Exception $e ) {
			$this->logger->error(
				'Failed to check article status',
				array(
					'entry_id' => $entry['id'],
					'error'    => $e->getMessage(),
				)
			);

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

	/**
	 * Fallback method to find article when primary endpoint returns 404.
	 *
	 * Tries alternative endpoints and updates the database with the correct
	 * type if the article is found, enabling self-healing for future checks.
	 *
	 * @since 2.8.2
	 *
	 * @param array  $entry          Calendar entry row from the database.
	 * @param string $tried_endpoint The endpoint that already returned 404.
	 * @return array|\WP_Error Article data array on success, WP_Error if not found.
	 */
	private function check_article_status_with_fallback( array $entry, $tried_endpoint ) {
		$article_id = $entry['wisewand_article_id'];

		$all_endpoints      = array( 'articles', 'discoverarticles', 'productpages', 'categorypages' );
		$fallback_endpoints = array_diff( $all_endpoints, array( $tried_endpoint ) );

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log(
				sprintf(
					'CRON FALLBACK: Trying %d alternative endpoints for article: %s',
					count( $fallback_endpoints ),
					$article_id
				)
			);
		}

		foreach ( $fallback_endpoints as $fallback_endpoint ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( sprintf( 'CRON FALLBACK: Trying %s endpoint...', $fallback_endpoint ) );
			}

			$this->rate_limiter->smart_delay( 'wisewand_api', 1.0 );

			$article = $this->wisewand_api->get_content( $fallback_endpoint, $article_id );

			if ( ! is_wp_error( $article ) ) {
				$this->logger->info(
					'Article found via fallback - database type mismatch corrected',
					array(
						'entry_id'         => $entry['id'],
						'article_id'       => $article_id,
						'wrong_endpoint'   => $tried_endpoint,
						'correct_endpoint' => $fallback_endpoint,
						'stored_type'      => $entry['wisewand_type'] ?? 'NOT SET',
					)
				);

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( sprintf( 'CRON FALLBACK: Found at %s endpoint! Correcting database...', $fallback_endpoint ) );
				}

				$this->correct_wisewand_type( $entry['id'], $fallback_endpoint, $entry['wisewand_type'] ?? 'blog' );

				return $article;
			}
		}

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( sprintf( 'CRON FALLBACK: Article not found on ANY endpoint: %s', $article_id ) );
		}

		return new \WP_Error(
			'wisewand_article_not_found_anywhere',
			__( 'Article could not be found on any API endpoint', 'wisewand-ai-content-writer-seo-content-generator' ),
			array(
				'article_id'      => $article_id,
				'endpoints_tried' => array_merge( array( $tried_endpoint ), array_values( $fallback_endpoints ) ),
			)
		);
	}

	/**
	 * Update wisewand_type in database when fallback discovers the correct endpoint.
	 *
	 * Self-healing logic to prevent future fallback attempts once the correct
	 * endpoint has been identified.
	 *
	 * @since 2.8.2
	 *
	 * @param int    $entry_id        Calendar entry ID.
	 * @param string $correct_endpoint The endpoint where the article was found.
	 * @param string $wrong_type      The incorrect type that was stored.
	 */
	private function correct_wisewand_type( $entry_id, $correct_endpoint, $wrong_type ) {
		$entry_id = absint( $entry_id );
		if ( $entry_id <= 0 ) {
			$this->logger->error(
				'Invalid entry_id in correct_wisewand_type',
				array( 'entry_id' => $entry_id )
			);
			return;
		}

		$endpoint_to_type = array(
			'categorypages'    => 'categorypages',
			'productpages'     => 'productpages',
			'discoverarticles' => 'discoverarticles',
			'articles'         => 'blog',
		);

		$correct_type = $endpoint_to_type[ $correct_endpoint ] ?? 'blog';

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

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table update for self-healing type correction
		$result = $wpdb->update(
			$table,
			array( 'wisewand_type' => $correct_type ),
			array( 'id' => $entry_id ),
			array( '%s' ),
			array( '%d' )
		);

		if ( false === $result ) {
			$this->logger->error(
				'Failed to correct wisewand_type in database',
				array(
					'entry_id'     => $entry_id,
					'correct_type' => $correct_type,
					'db_error'     => $wpdb->last_error,
				)
			);

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log(
					sprintf(
						'CRON: Failed to update wisewand_type for entry %d: %s',
						$entry_id,
						$wpdb->last_error
					)
				);
			}
		} else {
			$this->logger->info(
				'Auto-corrected wisewand_type in database',
				array(
					'entry_id' => $entry_id,
					'old_type' => $wrong_type,
					'new_type' => $correct_type,
				)
			);

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log(
					sprintf(
						'CRON: Corrected wisewand_type: "%s" -> "%s" for entry %d',
						$wrong_type,
						$correct_type,
						$entry_id
					)
				);
			}
		}
	}
}
