<?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 Article Generator
 *
 * Phase 1 of the CRON pipeline: queries calendar entries that need content
 * created and dispatches generation requests to the Wisewand API.
 *
 * @package Wisewand
 * @since 2.8.2
 */
class CRON_Article_Generator {

	/**
	 * 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;

	/**
	 * 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.
	 */
	public function __construct( Options $options, Logger $logger, $wisewand_api, $entry_manager, Rate_Limiter $rate_limiter ) {
		$this->options       = $options;
		$this->logger        = $logger;
		$this->wisewand_api  = $wisewand_api;
		$this->entry_manager = $entry_manager;
		$this->rate_limiter  = $rate_limiter;
	}

	/**
	 * 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 1: Generate articles for calendar entries that are not yet created.
	 *
	 * Queries entries with publication_date up to the configured lead time and
	 * dispatches API generation requests for each one found.
	 *
	 * @since 2.8.2
	 */
	public function step_1_generate_articles() {
		$lead_time   = $this->options->get( 'wisewand_cron_lead_time_days', 1 );
		$cutoff_date = gmdate( 'Y-m-d', strtotime( "+{$lead_time} days" ) );

		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 articles to generate
		$to_generate = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$table}
				WHERE publication_date <= %s
				AND wisewand_status = 'not_created'
				ORDER BY publication_date ASC, id ASC",
				$cutoff_date
			),
			ARRAY_A
		);

		$this->logger->info(
			'Step 1: Generate articles',
			array(
				'cutoff_date'    => $cutoff_date,
				'lead_time_days' => $lead_time,
				'count'          => count( $to_generate ),
				'includes'       => 'today, tomorrow, and past-due entries',
			)
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 1: Generating articles for dates up to ' . $cutoff_date . ' (found ' . count( $to_generate ) . ' entries)' );
		}

		if ( empty( $to_generate ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 1: No articles to generate' );
			}
			return;
		}

		foreach ( $to_generate as $entry ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'CRON Step 1: Generating entry ' . $entry['id'] . ' - ' . $entry['keyword'] . ' (scheduled for ' . $entry['publication_date'] . ')' );
			}

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

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

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 1: Completed generating ' . count( $to_generate ) . ' articles' );
		}
	}

	/**
	 * Generate a single article via the Wisewand API.
	 *
	 * Builds the clean payload, applies persona and project overrides, enables
	 * the webhook, sends the create request, and then triggers generation.
	 * On failure, updates the entry status to 'failed' with the error message.
	 *
	 * @since 2.8.2
	 *
	 * @param array $entry Calendar entry row from the database.
	 */
	public function generate_article( array $entry ) {
		if ( ! $this->wisewand_api ) {
			$this->logger->error( 'Wisewand API not configured' );
			return;
		}

		try {
			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wisewand_status' => 'creating',
				)
			);

			$payload = json_decode( $entry['wisewand_payload'], true );

			if ( empty( $payload ) ) {
				throw new \Exception( 'Payload is empty or invalid JSON' );
			}

			$endpoint     = $this->determine_endpoint( $entry['wisewand_type'] ?? 'blog' );
			$article_type = $payload['type'] ?? 'blog';

			if ( ! class_exists( 'Wisewand\\Utils\\API_Payload_Cleaner' ) ) {
				require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/API_Payload_Cleaner.php';
			}
			$clean_payload = \Wisewand\Utils\API_Payload_Cleaner::clean( $payload, $endpoint, $article_type );

			// Always override with current default persona (like manual does).
			$lang            = $payload['lang'] ?? 'en';
			$default_persona = $this->options->get( 'wisewand_default_persona_' . $lang, '' );

			if ( ! empty( $default_persona ) ) {
				$old_persona                         = $clean_payload['persona_id'] ?? 'none';
				$clean_payload['persona_id']         = $default_persona;
				$clean_payload['writing_style_mode'] = 'persona';
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON: Using current default persona_id: ' . $default_persona . ' (was: ' . $old_persona . ')' );
				}
			} else {
				$clean_payload['writing_style_mode'] = 'auto';
				unset( $clean_payload['persona_id'] );
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON: No persona configured, using auto writing mode' );
				}
			}

			// Always override with current default project (like manual does).
			$default_project = $this->options->get( 'wisewand_default_project_id', '' );
			if ( ! empty( $default_project ) ) {
				$old_project                 = $clean_payload['project_id'] ?? 'none';
				$clean_payload['project_id'] = $default_project;
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON: Using current default project_id: ' . $default_project . ' (was: ' . $old_project . ')' );
				}
			}

			// Enable webhook for instant notification when ready.
			if ( ! class_exists( 'Wisewand\\API\\Webhook_Receiver' ) ) {
				require_once WISEWAND_PLUGIN_DIR . 'includes/API/Webhook_Receiver.php';
			}

			$clean_payload['use_webhook']           = true;
			$clean_payload['webhook_connection_id'] = \Wisewand\API\Webhook_Receiver::get_webhook_url();

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '=== CRON WISEWAND API REQUEST ===' );
				error_log( 'Entry ID: ' . $entry['id'] );
				error_log( 'Endpoint: ' . $endpoint );
				error_log( 'Keyword: ' . $entry['keyword'] );
				error_log( 'Article Type: ' . $article_type );
				error_log( '===========================' );
			}

			$this->logger->info(
				'Creating Wisewand content (AUTOMATED CRON)',
				array(
					'entry_id'              => $entry['id'],
					'keyword'               => $entry['keyword'],
					'endpoint'              => $endpoint,
					'type'                  => $entry['wisewand_type'],
					'article_type'          => $article_type,
					'webhook_connection_id' => $clean_payload['webhook_connection_id'],
					'raw_fields'            => count( $payload ),
					'clean_fields'          => count( $clean_payload ),
				)
			);

			$article = $this->wisewand_api->create_content( $endpoint, $clean_payload );

			if ( is_wp_error( $article ) ) {
				$error_msg  = $article->get_error_message();
				$error_data = $article->get_error_data();

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( '=== CRON API ERROR ===' );
					error_log( 'Error Message: ' . $error_msg );
					error_log( 'Error Data: ' . print_r( $error_data, true ) );
					error_log( 'Persona ID in payload: ' . ( $clean_payload['persona_id'] ?? 'MISSING' ) );
					error_log( 'Project ID in payload: ' . ( $clean_payload['project_id'] ?? 'MISSING' ) );
					error_log( 'Writing Style Mode: ' . ( $clean_payload['writing_style_mode'] ?? 'MISSING' ) );
					error_log( '=====================' );
				}

				// Detect insufficient credits and provide actionable message.
				$is_credit_error = (
					( isset( $error_data['status_code'] ) && 402 === (int) $error_data['status_code'] ) ||
					false !== stripos( $error_msg, 'credit' ) ||
					false !== stripos( $error_msg, 'insufficient' ) ||
					false !== stripos( $error_msg, 'quota' )
				);

				if ( $is_credit_error ) {
					delete_transient( 'wisewand_credit_balance' );
					throw new \Exception( 'Insufficient WiseWand credits. Buy more at https://app.wisewand.ai/buy-credits' );
				}

				throw new \Exception( $error_msg . ' | Check if persona_id (' . ( $clean_payload['persona_id'] ?? 'missing' ) . ') and project_id (' . ( $clean_payload['project_id'] ?? 'missing' ) . ') are valid' );
			}

			// Invalidate credit balance cache after successful creation.
			delete_transient( 'wisewand_credit_balance' );

			$article_id = $article['id'] ?? $article['data']['id'] ?? null;

			if ( ! $article_id ) {
				throw new \Exception( 'No content ID returned from Wisewand' );
			}

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wisewand_article_id' => $article_id,
					'wisewand_status'     => 'creating',
				)
			);

			$this->logger->info(
				'Triggering content generation',
				array(
					'article_id' => $article_id,
					'endpoint'   => $endpoint,
				)
			);

			$run_result = $this->wisewand_api->run_content_generation( $endpoint, $article_id );

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

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wisewand_status'         => 'generating',
					'generation_triggered_at' => current_time( 'mysql' ),
				)
			);

			$this->logger->info(
				'Content generation triggered successfully',
				array(
					'entry_id'   => $entry['id'],
					'article_id' => $article_id,
					'endpoint'   => $endpoint,
				)
			);

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

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

	/**
	 * Determine which API endpoint to use based on content type.
	 *
	 * Maps wisewand_type values to their corresponding API endpoints.
	 * Handles legacy type names (e.g., 'categorypage' -> 'categorypages').
	 * All article subtypes (blog, news, affiliation-*) route to articles endpoint.
	 *
	 * @since 2.8.2
	 *
	 * @param string $wisewand_type Content type from database (blog, categorypages, productpages, discoverarticles, etc.).
	 * @return string API endpoint name (articles, categorypages, productpages, discoverarticles).
	 */
	public function determine_endpoint( $wisewand_type ) {
		if ( 'categorypage' === $wisewand_type || 'categorypages' === $wisewand_type ) {
			return 'categorypages';
		}
		if ( 'productpage' === $wisewand_type || 'productpages' === $wisewand_type ) {
			return 'productpages';
		}
		if ( 'discoverarticles' === $wisewand_type ) {
			return 'discoverarticles';
		}

		return 'articles';
	}

}
