<?php
/**
 * Health Alerts.
 *
 * Proactive monitoring for the Internal Link Health module. A weekly cron
 * event runs the scan in the background and compares it to the previous
 * snapshot. If internal-link health degrades — orphan count rises or broken
 * internal links appear — it raises:
 *
 *   - An admin notice on Helm/admin screens (always; zero-config).
 *   - An email to the site admin (only if the user opted in).
 *
 * Design notes:
 *   - The cron does the heavy scan, NOT page loads, so admin stays fast.
 *   - We store a compact snapshot (counts only) to compare against; we never
 *     diff full reports.
 *   - The admin notice is dismissible and clears once acknowledged or once a
 *     later scan shows health is no longer degrading.
 *   - The weekly schedule is registered defensively and cleared on
 *     deactivation / module disable so we never orphan a cron event.
 *
 * @package Platform\Helm\Modules\Link_Health
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\Link_Health;

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

final class HealthAlerts {

	public const CRON_HOOK = 'platform_helm_health_weekly_scan';
	private const SCHEDULE  = 'weekly';

	/** Compact snapshot of the previous scan, for comparison. */
	private const SNAPSHOT_OPTION = 'platform_helm_health_snapshot';
	/** The currently-active alert (so we can show a notice until resolved/dismissed). */
	private const ALERT_OPTION    = 'platform_helm_health_alert';
	/** Email opt-in flag, stored in the main settings option. */
	private const EMAIL_PREF_KEY  = 'health_alert_email';

	private const CAPABILITY = 'manage_options';

	public function __construct( private HealthAnalyzer $analyzer ) {}

	public function register_hooks(): void {
		add_action( self::CRON_HOOK, [ $this, 'run_scan' ] );
		add_action( 'admin_init', [ $this, 'maybe_schedule' ] );
		add_action( 'admin_notices', [ $this, 'maybe_render_notice' ] );
		add_action( 'admin_post_platform_helm_dismiss_health_alert', [ $this, 'handle_dismiss' ] );

		// Register a custom "weekly" schedule if the site doesn't define one.
		add_filter( 'cron_schedules', [ $this, 'add_weekly_schedule' ] );
	}

	/**
	 * @param array<string,array{interval:int,display:string}> $schedules
	 * @return array<string,array{interval:int,display:string}>
	 */
	public function add_weekly_schedule( array $schedules ): array {
		if ( ! isset( $schedules['weekly'] ) ) {
			$schedules['weekly'] = [
				'interval' => WEEK_IN_SECONDS,
				'display'  => __( 'Once Weekly', 'platform-helm' ),
			];
		}
		return $schedules;
	}

	public function maybe_schedule(): void {
		if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
			wp_schedule_event( time() + HOUR_IN_SECONDS, self::SCHEDULE, self::CRON_HOOK );
		}
	}

	public static function unschedule(): void {
		$ts = wp_next_scheduled( self::CRON_HOOK );
		if ( false !== $ts ) {
			wp_unschedule_event( $ts, self::CRON_HOOK );
		}
		wp_clear_scheduled_hook( self::CRON_HOOK );
	}

	/**
	 * The weekly job: force a fresh scan, compare to the prior snapshot, and
	 * raise an alert if health degraded.
	 */
	public function run_scan(): void {
		$report = $this->analyzer->get_report( true ); // force fresh.

		$current = [
			'orphans' => (int) ( $report['orphan_count'] ?? 0 ),
			'broken'  => (int) ( $report['broken_count'] ?? 0 ),
			'time'    => time(),
		];

		$previous = get_option( self::SNAPSHOT_OPTION, null );

		// Always store the latest snapshot for next time.
		update_option( self::SNAPSHOT_OPTION, $current, false );

		// No baseline yet — nothing to compare against on the first run.
		if ( ! is_array( $previous ) ) {
			return;
		}

		$orphans_up   = $current['orphans'] > (int) ( $previous['orphans'] ?? 0 );
		$broken_appeared = $current['broken'] > (int) ( $previous['broken'] ?? 0 );

		if ( ! $orphans_up && ! $broken_appeared ) {
			// Health stable or improving — clear any standing alert.
			delete_option( self::ALERT_OPTION );
			return;
		}

		$alert = [
			'orphans'       => $current['orphans'],
			'orphans_prev'  => (int) ( $previous['orphans'] ?? 0 ),
			'broken'        => $current['broken'],
			'broken_prev'   => (int) ( $previous['broken'] ?? 0 ),
			'orphans_up'    => $orphans_up,
			'broken_up'     => $broken_appeared,
			'time'          => time(),
		];
		update_option( self::ALERT_OPTION, $alert, false );

		if ( $this->email_enabled() ) {
			$this->send_email( $alert );
		}
	}

	private function email_enabled(): bool {
		$opts = get_option( 'platform_helm_settings', [] );
		return is_array( $opts ) && ! empty( $opts[ self::EMAIL_PREF_KEY ] );
	}

	/**
	 * @param array<string,mixed> $alert
	 */
	private function send_email( array $alert ): void {
		$to      = get_option( 'admin_email' );
		if ( ! $to ) {
			return;
		}
		$site    = wp_specialchars_decode( (string) get_option( 'blogname' ), ENT_QUOTES );
		$subject = sprintf(
			/* translators: %s: site name */
			__( '[%s] Internal link health needs attention', 'platform-helm' ),
			$site
		);

		$lines = [];
		$lines[] = sprintf(
			/* translators: %s: site name */
			__( 'Platform Helm ran its weekly internal-link health scan on %s and found a change worth reviewing:', 'platform-helm' ),
			$site
		);
		$lines[] = '';
		if ( ! empty( $alert['orphans_up'] ) ) {
			$lines[] = sprintf(
				/* translators: 1: previous orphan count, 2: current orphan count */
				__( '• Orphan pages rose from %1$d to %2$d.', 'platform-helm' ),
				(int) $alert['orphans_prev'],
				(int) $alert['orphans']
			);
		}
		if ( ! empty( $alert['broken_up'] ) ) {
			$lines[] = sprintf(
				/* translators: 1: previous broken count, 2: current broken count */
				__( '• Broken internal links rose from %1$d to %2$d.', 'platform-helm' ),
				(int) $alert['broken_prev'],
				(int) $alert['broken']
			);
		}
		$lines[] = '';
		$lines[] = __( 'Review the full report here:', 'platform-helm' );
		$lines[] = admin_url( 'admin.php?page=' . HealthPage::PAGE_SLUG );

		wp_mail( $to, $subject, implode( "\n", $lines ) );
	}

	/* ===================================================================== *
	 * ADMIN NOTICE
	 * ===================================================================== */

	public function maybe_render_notice(): void {
		if ( ! current_user_can( self::CAPABILITY ) ) {
			return;
		}
		$alert = get_option( self::ALERT_OPTION, null );
		if ( ! is_array( $alert ) ) {
			return;
		}

		$parts = [];
		if ( ! empty( $alert['orphans_up'] ) ) {
			$parts[] = sprintf(
				/* translators: 1: previous count, 2: current count */
				__( 'orphan pages rose from %1$d to %2$d', 'platform-helm' ),
				(int) $alert['orphans_prev'],
				(int) $alert['orphans']
			);
		}
		if ( ! empty( $alert['broken_up'] ) ) {
			$parts[] = sprintf(
				/* translators: 1: previous count, 2: current count */
				__( 'broken internal links rose from %1$d to %2$d', 'platform-helm' ),
				(int) $alert['broken_prev'],
				(int) $alert['broken']
			);
		}
		if ( empty( $parts ) ) {
			return;
		}

		$report_url  = admin_url( 'admin.php?page=' . HealthPage::PAGE_SLUG );
		$dismiss_url = wp_nonce_url(
			admin_url( 'admin-post.php?action=platform_helm_dismiss_health_alert' ),
			'platform_helm_dismiss_health_alert'
		);
		?>
		<div class="notice notice-warning is-dismissible">
			<p>
				<strong><?php esc_html_e( 'Platform Helm — link health alert:', 'platform-helm' ); ?></strong>
				<?php
				/* translators: %s: comma-separated list of issues */
				echo esc_html( sprintf( __( 'Since the last scan, %s.', 'platform-helm' ), implode( __( ', and ', 'platform-helm' ), $parts ) ) );
				?>
				&nbsp;<a href="<?php echo esc_url( $report_url ); ?>"><?php esc_html_e( 'View report', 'platform-helm' ); ?></a>
				&nbsp;|&nbsp;<a href="<?php echo esc_url( $dismiss_url ); ?>"><?php esc_html_e( 'Dismiss', 'platform-helm' ); ?></a>
			</p>
		</div>
		<?php
	}

	public function handle_dismiss(): void {
		if ( ! current_user_can( self::CAPABILITY ) ) {
			wp_die( esc_html__( 'Permission denied.', 'platform-helm' ) );
		}
		check_admin_referer( 'platform_helm_dismiss_health_alert' );
		delete_option( self::ALERT_OPTION );
		wp_safe_redirect( wp_get_referer() ?: admin_url() );
		exit;
	}
}
