<?php
/**
 * Weekly summary digest for Mail Doctor.
 *
 * @package MailDoctor
 */

namespace MailDoctor;

use DateTimeImmutable;
use DateTimeZone;
use MailDoctor\Helpers\Settings;
use MailDoctor\Mailer;

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

/**
 * Handles scheduling and delivering weekly email summaries.
 */
class Digest {


	/**
	 * Cron hook identifier.
	 */
	const CRON_HOOK = 'mail_doctor_weekly_digest';

	/**
	 * Initialize hooks.
	 *
	 * @return void
	 */
	public static function init(): void {
		add_action( self::CRON_HOOK, array( __CLASS__, 'dispatch' ) );
		add_action( 'update_option_' . Settings::OPTION_KEY, array( __CLASS__, 'maybe_reschedule' ), 10, 2 );
		self::maybe_schedule();
	}

	/**
	 * Schedule cron on activation.
	 *
	 * @return void
	 */
	public static function activate(): void {
		self::maybe_schedule( true );
	}

	/**
	 * Remove scheduled events on deactivation.
	 *
	 * @return void
	 */
	public static function deactivate(): void {
		$timestamp = wp_next_scheduled( self::CRON_HOOK );
		while ( false !== $timestamp ) {
			wp_unschedule_event( $timestamp, self::CRON_HOOK );
			$timestamp = wp_next_scheduled( self::CRON_HOOK );
		}
	}

	/**
	 * Reschedule digest when settings updated.
	 *
	 * @param mixed $old Old option value.
	 * @param mixed $new New option value.
	 * @return void
	 */
	public static function maybe_reschedule( $old, $new ): void
	{ // phpcs:ignore
		self::maybe_schedule( true );
	}

	/**
	 * Ensure cron event is scheduled as required.
	 *
	 * @param bool $force Whether to recalculate schedule.
	 * @return void
	 */
	protected static function maybe_schedule( bool $force = false ): void {
		$settings = Settings::all();

		if ( empty( $settings['digest_enabled'] ) ) {
			self::deactivate();
			return;
		}

		if ( $force ) {
			self::deactivate();
		}

		if ( wp_next_scheduled( self::CRON_HOOK ) ) {
			return;
		}

		$timestamp = self::next_run_timestamp( (string) $settings['digest_weekday'], (int) $settings['digest_hour'] );

		if ( $timestamp <= 0 ) {
			return;
		}

		wp_schedule_event( $timestamp, 'weekly', self::CRON_HOOK );
	}

	/**
	 * Determine next run timestamp based on weekday/hour.
	 *
	 * @param string $weekday Weekday slug (monday ... sunday).
	 * @param int    $hour    Hour of day (0-23).
	 * @return int Unix timestamp.
	 */
	protected static function next_run_timestamp( string $weekday, int $hour ): int {
		$weekday = strtolower( $weekday );
		$allowed = array( 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday' );

		if ( ! in_array( $weekday, $allowed, true ) ) {
			$weekday = 'monday';
		}

		$hour = max( 0, min( 23, $hour ) );

		$timezone = wp_timezone();
		$now      = new DateTimeImmutable( 'now', $timezone );
		$target   = $now->setTime( $hour, 0 );

		while ( strtolower( $target->format( 'l' ) ) !== $weekday || $target <= $now ) {
			$target = $target->modify( '+1 day' )->setTime( $hour, 0 );
		}

		return $target->setTimezone( new DateTimeZone( 'UTC' ) )->getTimestamp();
	}

	/**
	 * Dispatch the weekly digest email.
	 *
	 * @return void
	 */
	public static function dispatch(): void {
		$settings = Settings::all();

		if ( empty( $settings['digest_enabled'] ) ) {
			self::deactivate();
			return;
		}

		$recipients = self::resolve_recipients( $settings );

		if ( empty( $recipients ) ) {
			return;
		}

		$summary = self::build_summary();

		if ( empty( $summary['body'] ) ) {
			return;
		}

		Mailer::force_next_override();
		Mailer::set_event_context( 'mail_doctor_digest' );
		Mailer::set_email_id( 'digest' );

		try {
			wp_mail(
				$recipients,
				$summary['subject'],
				$summary['body'],
				array( 'Content-Type: text/html; charset=UTF-8' )
			);
		} finally {
			Mailer::clear_event_context();
			Mailer::cleanup_context();
		}

		self::maybe_schedule( true );
	}

	/**
	 * Resolve digest recipients from settings.
	 *
	 * @param array<string,mixed> $settings Plugin settings.
	 * @return array<int,string>
	 */
	protected static function resolve_recipients( array $settings ): array {
		$list = array();

		if ( ! empty( $settings['digest_recipients'] ) ) {
			$list = array_map( 'trim', explode( ',', (string) $settings['digest_recipients'] ) );
		} elseif ( ! empty( $settings['alert_email'] ) ) {
			$list = array( $settings['alert_email'] );
		} else {
			$list = array( get_option( 'admin_email' ) );
		}

		$emails = array();

		foreach ( $list as $entry ) {
			$email = sanitize_email( $entry );
			if ( $email && is_email( $email ) ) {
				$emails[] = $email;
			}
		}

		return array_values( array_unique( $emails ) );
	}

	/**
	 * Compose digest subject/body.
	 *
	 * @return array{subject:string,body:string}
	 */
	protected static function build_summary(): array {
		global $wpdb;

		$table = Logger::table_name();

		$end   = gmdate( 'Y-m-d H:i:s' );
		$start = gmdate( 'Y-m-d H:i:s', time() - WEEK_IN_SECONDS );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$counts = $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				"SELECT status, COUNT(*) AS total FROM {$table} WHERE created_at BETWEEN %s AND %s GROUP BY status",
				$start,
				$end
			),
			ARRAY_A
		);

		$totals = array(
			'sent'    => 0,
			'failed'  => 0,
			'queued'  => 0,
			'skipped' => 0,
		);

		foreach ( $counts as $row ) {
			$status = $row['status'] ?? '';
			$total  = (int) ( $row['total'] ?? 0 );

			if ( isset( $totals[ $status ] ) ) {
				$totals[ $status ] = $total;
			}
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$latency = $wpdb->get_row(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				"SELECT AVG(duration_ms) AS average, MAX(duration_ms) AS peak FROM {$table} WHERE created_at BETWEEN %s AND %s AND duration_ms IS NOT NULL",
				$start,
				$end
			),
			ARRAY_A
		);

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$failures = $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				"SELECT event, COUNT(*) AS total FROM {$table} WHERE created_at BETWEEN %s AND %s AND status = 'failed' GROUP BY event ORDER BY total DESC LIMIT 5",
				$start,
				$end
			),
			ARRAY_A
		);

		$total_messages = array_sum( $totals );

		if ( 0 === $total_messages ) {
			return array(
				'subject' => sprintf(
					/* translators: %s site name. */
					__( 'Mail Doctor Weekly Summary – %s', 'mail-doctor' ),
					wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES )
				),
				'body'    => '',
			);
		}

		$failure_rate = $total_messages > 0 ? ( $totals['failed'] / $total_messages ) * 100 : 0;
		$avg_latency  = isset( $latency['average'] ) ? round( (float) $latency['average'], 2 ) : 0;
		$peak_latency = isset( $latency['peak'] ) ? (int) $latency['peak'] : 0;

		$subject = sprintf(
			/* translators: 1: site name, 2: sent count */
			__( 'Mail Doctor Weekly Summary – %1$s (%2$d sent)', 'mail-doctor' ),
			wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
			$totals['sent']
		);

		$body = '<h2>' . esc_html__( 'Weekly Delivery Summary', 'mail-doctor' ) . '</h2>';
		/* translators: 1: start date, 2: end date */
		$body .= '<p>' . esc_html( sprintf( __( 'Window: %1$s → %2$s (UTC)', 'mail-doctor' ), $start, $end ) ) . '</p>';

		$body .= '<table style="border-collapse:collapse;border:1px solid #ddd;width:100%;max-width:520px;">';
		$body .= '<thead><tr>';
		$body .= '<th style="border:1px solid #ddd;padding:6px;text-align:left;">' . esc_html__( 'Metric', 'mail-doctor' ) . '</th>';
		$body .= '<th style="border:1px solid #ddd;padding:6px;text-align:right;">' . esc_html__( 'Value', 'mail-doctor' ) . '</th>';
		$body .= '</tr></thead><tbody>';
		$body .= self::render_row( __( 'Sent', 'mail-doctor' ), number_format_i18n( $totals['sent'] ) );
		$body .= self::render_row( __( 'Failed', 'mail-doctor' ), number_format_i18n( $totals['failed'] ) );
		$body .= self::render_row( __( 'Queued', 'mail-doctor' ), number_format_i18n( $totals['queued'] ) );
		$body .= self::render_row( __( 'Skipped', 'mail-doctor' ), number_format_i18n( $totals['skipped'] ) );
		$body .= self::render_row( __( 'Failure rate', 'mail-doctor' ), sprintf( '%.2f%%', $failure_rate ) );
		$body .= self::render_row( __( 'Avg latency (ms)', 'mail-doctor' ), number_format_i18n( (float) $avg_latency ) );
		$body .= self::render_row( __( 'Peak latency (ms)', 'mail-doctor' ), number_format_i18n( $peak_latency ) );
		$body .= '</tbody></table>';

		if ( ! empty( $failures ) ) {
			$body .= '<h3 style="margin-top:18px;">' . esc_html__( 'Top failing events', 'mail-doctor' ) . '</h3><ul>';

			foreach ( $failures as $failure ) {
				$event = ! empty( $failure['event'] ) ? sanitize_text_field( $failure['event'] ) : __( '(unknown)', 'mail-doctor' );
				$count = number_format_i18n( (int) $failure['total'] );

				/* translators: %s: number of failures */
				$body .= '<li><strong>' . esc_html( $event ) . ':</strong> ' . esc_html( sprintf( _n( '%s failure', '%s failures', (int) $failure['total'], 'mail-doctor' ), $count ) ) . '</li>';
			}

			$body .= '</ul>';
		}

		$body .= '<p style="margin-top:18px;">' . esc_html__( 'You are receiving this email because weekly summaries are enabled in Mail Doctor settings.', 'mail-doctor' ) . '</p>';

		return array(
			'subject' => $subject,
			'body'    => $body,
		);
	}

	/**
	 * Render HTML table row.
	 *
	 * @param string $label Metric label.
	 * @param string $value Metric value.
	 * @return string
	 */
	protected static function render_row( string $label, string $value ): string {
		return sprintf(
			'<tr><td style="border:1px solid #ddd;padding:6px;">%1$s</td><td style="border:1px solid #ddd;padding:6px;text-align:right;">%2$s</td></tr>',
			esc_html( $label ),
			esc_html( $value )
		);
	}
}
