<?php

namespace SerpIQ\Cron;

use SerpIQ\Cron\Alerts;
use SerpIQ\Gsc\Backfill;
use SerpIQ\Gsc\Sync;
use SerpIQ\Links\Internal_Links;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Scheduled jobs (WP-Cron). Not reachable via admin $_GET/$_POST; no WP nonce layer here.
 * Assumes WordPress’s cron event store is trusted; hooks run without an HTTP user context.
 */
final class Scheduler {
	private static ?Scheduler $instance = null;

	public static function instance(): Scheduler {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	public function init(): void {
		add_action( 'init', array( $this, 'register_event' ) );
		add_action( 'serpiq_daily_sync', array( $this, 'run_daily_sync' ) );
		add_action( 'serpiq_backfill_step', array( $this, 'run_backfill_step' ) );
		add_action( 'serpiq_internal_links_daily', array( $this, 'run_internal_links_index' ) );
	}

	public function register_event(): void {
		if ( ! wp_next_scheduled( 'serpiq_daily_sync' ) ) {
			wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'serpiq_daily_sync' );
		}

		if ( ! wp_next_scheduled( 'serpiq_internal_links_daily' ) ) {
			wp_schedule_event( time() + 2 * HOUR_IN_SECONDS, 'daily', 'serpiq_internal_links_daily' );
		}
	}

	public function activate(): void {
		$this->register_event();
	}

	public function deactivate(): void {
		$timestamp = wp_next_scheduled( 'serpiq_daily_sync' );
		if ( $timestamp ) {
			wp_unschedule_event( $timestamp, 'serpiq_daily_sync' );
		}

		$ts_links = wp_next_scheduled( 'serpiq_internal_links_daily' );
		if ( $ts_links ) {
			wp_unschedule_event( $ts_links, 'serpiq_internal_links_daily' );
		}
	}

	public function run_daily_sync(): void {
		Sync::instance()->run_last_3_days();
		Alerts::instance()->run();
	}

	public function run_backfill_step(): void {
		$time_start = microtime( true );
		$status     = 'success';
		$error      = null;

		try {
			Backfill::instance()->run_next_batch();
		} catch ( \Throwable $e ) {
			$status = 'error';
			$error  = $e->getMessage();
		}

		$duration_ms = (int) round( ( microtime( true ) - $time_start ) * 1000 );

		// Always log that the cron step fired (even if run_next_batch does nothing),
		// so support can distinguish "cron not firing" from "import failing".
		try {
			$today = gmdate( 'Y-m-d' );
			Sync::instance()->log_sync(
				$today,
				$today,
				0,
				$status,
				$error,
				$duration_ms,
				array(),
				'backfill_step'
			);
		} catch ( \Throwable $e ) {
			// Best-effort logging only.
		}
	}

	public function run_internal_links_index(): void {
		$time_start = microtime( true );
		$status     = 'success';
		$error      = null;
		$links      = 0;

		try {
			// Rebuild the internal links graph daily for all public post types.
			$result = Internal_Links::instance()->rebuild_index(
				array(
					// Use default post_types & batch_size logic inside the indexer.
				)
			);
			if ( is_array( $result ) && isset( $result['links'] ) ) {
				$links = (int) $result['links'];
			}
		} catch ( \Throwable $e ) {
			$status = 'error';
			$error  = $e->getMessage();
		}

		$duration_ms = (int) round( ( microtime( true ) - $time_start ) * 1000 );

		// Log the internal links cron run into the shared sync log for diagnostics.
		try {
			$today = gmdate( 'Y-m-d' );
			Sync::instance()->log_sync(
				$today,
				$today,
				$links,
				$status,
				$error,
				$duration_ms,
				array(),
				'internal_links_daily'
			);
		} catch ( \Throwable $e ) {
			// Logging must never break the cron job; fail silently here.
		}
	}
}

