<?php
/**
 * Post Puller — pull pending content from the premium backend and insert
 * locally via wp_insert_post().
 *
 * Why this exists: backend → customer-WP push is increasingly blocked by
 * host-level bot detection (Cloudflare, SiteGround, Litespeed). When the
 * plugin pulls instead of the backend pushing, the request originates
 * from the customer's own server and the host's bot wall never sees it.
 * See workplans/active/FIX-PluginPullsContent.md for full design.
 *
 * @package Simple_Ai_Auto_Post
 * @since 2.1.0
 */

if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Hourly cron: pull any pending posts from the backend and insert them
 * locally as drafts (or scheduled future posts).
 */
function saiap_schedule_post_puller() {
	if ( ! wp_next_scheduled( 'saiap_pull_pending_posts' ) ) {
		wp_schedule_event( time() + 60, 'hourly', 'saiap_pull_pending_posts' );
	}
}
add_action( 'init', 'saiap_schedule_post_puller' );

/**
 * Unschedule on plugin deactivation so the cron entry doesn't linger.
 */
function saiap_unschedule_post_puller() {
	$timestamp = wp_next_scheduled( 'saiap_pull_pending_posts' );
	if ( $timestamp ) {
		wp_unschedule_event( $timestamp, 'saiap_pull_pending_posts' );
	}
}
register_deactivation_hook( SAIAP_PLUGIN_FILE, 'saiap_unschedule_post_puller' );

/**
 * The cron action handler. Fetches pending items from the backend and
 * inserts each as a local draft (or future-scheduled post). Reports back
 * to the backend after each successful insert so the row's wp_post_id is
 * set and the same item isn't returned again.
 *
 * Safe to call multiple times; idempotent via post meta lookup.
 */
function saiap_do_pull_pending_posts() {
	$license_key = get_option( 'saiap_license_key', '' );
	if ( empty( $license_key ) ) {
		return;
	}

	$domain = saiap_get_server_host();

	$response = wp_remote_post(
		SAIAP_PREMIUM_API_URL . '/api/trial/pending-posts',
		array(
			'timeout' => 30,
			'body'    => wp_json_encode(
				array(
					'domain'         => $domain,
					'license_key'    => $license_key,
					'plugin_version' => defined( 'SAIAP_VERSION' ) ? SAIAP_VERSION : null,
				)
			),
			'headers' => array(
				'Content-Type' => 'application/json',
			),
		)
	);

	if ( is_wp_error( $response ) ) {
		error_log( '[saiap_post_puller] backend request failed: ' . $response->get_error_message() );
		return;
	}

	$code = wp_remote_retrieve_response_code( $response );
	if ( 200 !== $code ) {
		error_log( '[saiap_post_puller] backend returned ' . $code );
		return;
	}

	$body = json_decode( wp_remote_retrieve_body( $response ), true );
	if ( ! is_array( $body ) || empty( $body['items'] ) || ! is_array( $body['items'] ) ) {
		return;
	}

	$results = array(
		'inserted' => 0,
		'skipped'  => 0,
		'errors'   => 0,
	);

	foreach ( $body['items'] as $item ) {
		$result = saiap_insert_pulled_post( $item, $domain, $license_key );
		if ( 'inserted' === $result ) {
			++$results['inserted'];
		} elseif ( 'skipped' === $result ) {
			++$results['skipped'];
		} else {
			++$results['errors'];
		}
	}

	error_log( sprintf(
		'[saiap_post_puller] domain=%s inserted=%d skipped=%d errors=%d',
		$domain,
		$results['inserted'],
		$results['skipped'],
		$results['errors']
	) );
}
add_action( 'saiap_pull_pending_posts', 'saiap_do_pull_pending_posts' );

/**
 * Insert one pulled item as a local post and report back. Dedupes via
 * the `_clearpost_calendar_item_id` post meta.
 *
 * @param array  $item        Item from the backend pending-posts response.
 * @param string $domain      Site domain (for the report-back call).
 * @param string $license_key License key.
 * @return string 'inserted' | 'skipped' | 'error'
 */
function saiap_insert_pulled_post( $item, $domain, $license_key ) {
	$calendar_item_id = isset( $item['calendarItemId'] ) ? sanitize_text_field( $item['calendarItemId'] ) : '';
	$title            = isset( $item['title'] ) ? sanitize_text_field( $item['title'] ) : '';
	$content          = isset( $item['content'] ) ? $item['content'] : '';
	$scheduled_for    = isset( $item['scheduledFor'] ) ? sanitize_text_field( $item['scheduledFor'] ) : '';
	$meta_description = isset( $item['metaDescription'] ) ? sanitize_textarea_field( $item['metaDescription'] ) : '';
	$seo_title        = isset( $item['seoTitle'] ) ? sanitize_text_field( $item['seoTitle'] ) : '';
	$focus_keyword    = isset( $item['focusKeyword'] ) ? sanitize_text_field( $item['focusKeyword'] ) : '';
	$excerpt          = isset( $item['excerpt'] ) ? sanitize_textarea_field( $item['excerpt'] ) : '';

	if ( empty( $calendar_item_id ) || empty( $content ) ) {
		return 'error';
	}

	// Idempotency: skip if a post with this calendar_item_id already exists locally.
	$existing = get_posts(
		array(
			'meta_key'       => '_clearpost_calendar_item_id',
			'meta_value'     => $calendar_item_id,
			'post_type'      => 'post',
			'post_status'    => 'any',
			'posts_per_page' => 1,
			'fields'         => 'ids',
		)
	);
	if ( ! empty( $existing ) ) {
		// Already inserted — report back to backend in case the previous report didn't land.
		saiap_report_post_created( $domain, $license_key, $calendar_item_id, (int) $existing[0] );
		return 'skipped';
	}

	// Decide post status from scheduled_for:
	// - If scheduled_for is in the future, schedule it (future status).
	// - Otherwise insert as draft for the customer to review and publish.
	$post_status   = 'draft';
	$post_date     = current_time( 'mysql' );           // site-local
	$post_date_gmt = current_time( 'mysql', true );     // UTC
	if ( ! empty( $scheduled_for ) ) {
		$scheduled_ts = strtotime( $scheduled_for );
		if ( $scheduled_ts && $scheduled_ts > time() ) {
			$post_status   = 'future';
			$post_date_gmt = gmdate( 'Y-m-d H:i:s', $scheduled_ts );
			$post_date     = get_date_from_gmt( $post_date_gmt );
		}
	}

	$postarr = array(
		'post_title'    => $title ? $title : __( 'Untitled', 'clearpost-simple-ai-auto-post' ),
		'post_content'  => $content,
		'post_status'   => $post_status,
		'post_type'     => 'post',
		'post_date'     => $post_date,
		'post_date_gmt' => $post_date_gmt,
		'meta_input'    => array(
			'_clearpost_calendar_item_id' => $calendar_item_id,
			'_clearpost_pulled_at'        => current_time( 'mysql' ),
		),
	);

	if ( ! empty( $excerpt ) ) {
		$postarr['post_excerpt'] = $excerpt;
	}

	$post_id = wp_insert_post( $postarr, true );
	if ( is_wp_error( $post_id ) || ! $post_id ) {
		error_log( '[saiap_post_puller] wp_insert_post failed for calendar_item ' . $calendar_item_id );
		return 'error';
	}

	// FAQ schema (if provided) goes into the same post meta key the plugin's
	// existing wp_head hook (saiap_output_faq_schema) already reads from.
	// Stored raw because it's a JSON-LD string — sanitizing as HTML would
	// corrupt the JSON. This matches api-insert-post.php's behavior.
	if ( ! empty( $item['faqSchema'] ) ) {
		update_post_meta( $post_id, '_clearpost_faq_schema', $item['faqSchema'] );
	}

	// SEO fields — route through the shared dispatcher so detection
	// (Yoast / Rank Math / AIOSEO / none) + primary_category + durable
	// _clearpost_* backup keys are handled identically to the push path.
	// Defined in api-insert-post.php which loads before this file.
	if ( function_exists( 'saiap_apply_seo_meta' ) ) {
		saiap_apply_seo_meta( $post_id, $meta_description, $focus_keyword, null, $seo_title );
	}

	// Report back so the backend marks the calendar row as delivered (sets wp_post_id).
	saiap_report_post_created( $domain, $license_key, $calendar_item_id, (int) $post_id );

	// Trigger image agent for the pulled post (sessionless — backend builds
	// a synthetic session from the post content we pass along).
	if ( function_exists( 'saiap_trigger_image_agent_sessionless' ) ) {
		saiap_trigger_image_agent_sessionless( (int) $post_id, $title, $content );
	}

	return 'inserted';
}

/**
 * Tell the backend that the plugin successfully created a post for a
 * calendar item. Backend sets wp_post_id so subsequent polls don't
 * return the same item and so the cron's push path skips it.
 *
 * @param string $domain
 * @param string $license_key
 * @param string $calendar_item_id
 * @param int    $wp_post_id
 * @return bool true if reported successfully, false otherwise
 */
function saiap_report_post_created( $domain, $license_key, $calendar_item_id, $wp_post_id ) {
	$response = wp_remote_post(
		SAIAP_PREMIUM_API_URL . '/api/trial/post-created',
		array(
			'timeout' => 15,
			'body'    => wp_json_encode(
				array(
					'domain'           => $domain,
					'license_key'      => $license_key,
					'calendar_item_id' => $calendar_item_id,
					'wp_post_id'       => $wp_post_id,
				)
			),
			'headers' => array(
				'Content-Type' => 'application/json',
			),
		)
	);

	if ( is_wp_error( $response ) ) {
		error_log( '[saiap_post_puller] report-back failed: ' . $response->get_error_message() );
		return false;
	}

	$code = wp_remote_retrieve_response_code( $response );
	if ( 200 !== $code ) {
		error_log( '[saiap_post_puller] report-back returned ' . $code );
		return false;
	}

	return true;
}

/**
 * AJAX: "Pull pending posts now" admin button handler. Triggers an
 * immediate puller run (instead of waiting for the hourly cron) and
 * reports the result counts back to the JS.
 */
function saiap_ajax_pull_pending_posts_now() {
	if ( ! check_ajax_referer( 'saiap_pull_pending_posts', 'nonce', false ) ) {
		wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'clearpost-simple-ai-auto-post' ) ), 403 );
	}

	if ( ! current_user_can( 'manage_options' ) ) {
		wp_send_json_error( array( 'message' => __( 'Permission denied.', 'clearpost-simple-ai-auto-post' ) ), 403 );
	}

	$license_key = get_option( 'saiap_license_key', '' );
	if ( empty( $license_key ) ) {
		wp_send_json_error( array( 'message' => __( 'No license key configured.', 'clearpost-simple-ai-auto-post' ) ), 400 );
	}

	$domain = saiap_get_server_host();

	$response = wp_remote_post(
		SAIAP_PREMIUM_API_URL . '/api/trial/pending-posts',
		array(
			'timeout' => 30,
			'body'    => wp_json_encode(
				array(
					'domain'         => $domain,
					'license_key'    => $license_key,
					'plugin_version' => defined( 'SAIAP_VERSION' ) ? SAIAP_VERSION : null,
				)
			),
			'headers' => array( 'Content-Type' => 'application/json' ),
		)
	);

	if ( is_wp_error( $response ) ) {
		wp_send_json_error( array( 'message' => $response->get_error_message() ), 500 );
	}

	$body = json_decode( wp_remote_retrieve_body( $response ), true );
	if ( ! is_array( $body ) || ! isset( $body['items'] ) ) {
		wp_send_json_error( array( 'message' => __( 'Unexpected response from backend.', 'clearpost-simple-ai-auto-post' ) ), 500 );
	}

	$results = array(
		'inserted' => 0,
		'skipped'  => 0,
		'errors'   => 0,
	);
	foreach ( $body['items'] as $item ) {
		$result = saiap_insert_pulled_post( $item, $domain, $license_key );
		if ( 'inserted' === $result ) {
			++$results['inserted'];
		} elseif ( 'skipped' === $result ) {
			++$results['skipped'];
		} else {
			++$results['errors'];
		}
	}

	wp_send_json_success(
		array(
			'message'  => sprintf(
				/* translators: 1: inserted count, 2: skipped count, 3: errors count */
				__( 'Pulled %1$d new post(s). Skipped %2$d already-imported, %3$d error(s).', 'clearpost-simple-ai-auto-post' ),
				$results['inserted'],
				$results['skipped'],
				$results['errors']
			),
			'inserted' => $results['inserted'],
			'skipped'  => $results['skipped'],
			'errors'   => $results['errors'],
		)
	);
}
add_action( 'wp_ajax_saiap_pull_pending_posts_now', 'saiap_ajax_pull_pending_posts_now' );
