<?php
/**
 * PK Core - the Alerts settings screen.
 *
 * Settings only. There is deliberately no alert dashboard: the failure this
 * module exists to correct is that nobody looks at dashboards, and adding one
 * would reintroduce it. The audit trail lives in the alerts table, where it
 * can be queried when a question is actually being asked.
 *
 * Self-contained by design - it must render on a site that activates the
 * notify module alone, where the profile and topics screens are absent.
 *
 * @package PublisherKits\Core
 * @since   0.9.0
 */

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

if ( ! class_exists( 'PKCORE_Alerts_Admin' ) ) {

	/**
	 * Class PKCORE_Alerts_Admin
	 *
	 * @since 0.9.0
	 */
	class PKCORE_Alerts_Admin {

		/** This screen's slug. */
		const SLUG = 'publisherkits-alerts';

		/** Transient holding the validation outcome of the last save. */
		const NOTICE_TRANSIENT = 'pkcore_alerts_notice';

		/**
		 * Hook in.
		 *
		 * @return void
		 */
		public static function init() {
			add_action( 'admin_menu', array( __CLASS__, 'register_menu' ), 9 );
			add_action( 'admin_post_pkcore_save_alerts', array( __CLASS__, 'handle_save' ) );
			add_action( 'admin_post_pkcore_test_alert', array( __CLASS__, 'handle_test' ) );
			add_filter( 'pkcore_setup_links', array( __CLASS__, 'add_setup_link' ) );
		}

		/**
		 * Register the Alerts screen, creating the shared PublisherKits menu
		 * only if no sibling plugin has already claimed it.
		 *
		 * @return void
		 */
		public static function register_menu() {
			global $admin_page_hooks;

			if ( empty( $admin_page_hooks[ self::menu_slug() ] ) ) {
				add_menu_page(
					__( 'PublisherKits', 'publisherkits-socialpromoter' ),
					__( 'PublisherKits', 'publisherkits-socialpromoter' ),
					'manage_options',
					self::menu_slug(),
					array( __CLASS__, 'render' ),
					'dashicons-analytics',
					58
				);
			}

			add_submenu_page(
				self::menu_slug(),
				__( 'Alerts', 'publisherkits-socialpromoter' ),
				__( 'Alerts', 'publisherkits-socialpromoter' ),
				'manage_options',
				self::SLUG,
				array( __CLASS__, 'render' )
			);
		}

		/**
		 * The shared PublisherKits menu slug, without depending on
		 * PKCORE_Admin being loaded (it is not, on a notify-only site).
		 *
		 * @return string
		 */
		public static function menu_slug() {
			return class_exists( 'PKCORE_Admin' ) ? PKCORE_Admin::MENU_SLUG : 'publisherkits';
		}

		/**
		 * Add this screen to the shared setup strip.
		 *
		 * @param array[] $links Existing links.
		 * @return array[]
		 */
		public static function add_setup_link( $links ) {
			$links   = (array) $links;
			$links[] = array(
				'slug'  => self::SLUG,
				'label' => __( 'Alerts', 'publisherkits-socialpromoter' ),
			);
			return $links;
		}

		// ====== Render ======

		/**
		 * Render the settings screen.
		 *
		 * @return void
		 */
		public static function render() {
			if ( ! current_user_can( 'manage_options' ) ) {
				return;
			}

			$raw_recipients = (string) get_option( PKCORE_Alerts::OPT_RECIPIENTS, '' );
			$resolved       = PKCORE_Alerts::recipients();
			$admin_email    = (string) get_option( 'admin_email', '' );

			self::render_notice();
			?>
			<div class="wrap">
				<h1><?php esc_html_e( 'PublisherKits Alerts', 'publisherkits-socialpromoter' ); ?></h1>

				<p class="description" style="max-width:46em;">
					<?php esc_html_e( 'Detection rules across Engagement Signals, ES Intelligence and Publisher Tools raise alerts here. Critical alerts are emailed as they happen; warnings and information roll into one daily digest. Every alert is recorded whether or not it is emailed.', 'publisherkits-socialpromoter' ); ?>
				</p>

				<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
					<input type="hidden" name="action" value="pkcore_save_alerts" />
					<?php wp_nonce_field( 'pkcore_save_alerts', 'pkcore_alerts_nonce' ); ?>

					<table class="form-table" role="presentation">
						<tr>
							<th scope="row">
								<label for="pkcore_alert_recipients"><?php esc_html_e( 'Recipients', 'publisherkits-socialpromoter' ); ?></label>
							</th>
							<td>
								<textarea name="pkcore_alert_recipients" id="pkcore_alert_recipients"
									rows="3" class="large-text code"
									placeholder="editor@example.com, ops@example.com"><?php echo esc_textarea( $raw_recipients ); ?></textarea>
								<p class="description">
									<?php esc_html_e( 'Comma-separated. Who needs to know differs by site and by title, so set the people who will actually act on it rather than leaving it to the site administrator.', 'publisherkits-socialpromoter' ); ?>
								</p>
								<p class="description">
									<?php if ( '' === trim( $raw_recipients ) ) : ?>
										<strong><?php esc_html_e( 'Currently sending to the site administrator:', 'publisherkits-socialpromoter' ); ?></strong>
										<code><?php echo esc_html( $admin_email ); ?></code>
									<?php else : ?>
										<strong><?php esc_html_e( 'Currently sending to:', 'publisherkits-socialpromoter' ); ?></strong>
										<code><?php echo esc_html( implode( ', ', $resolved ) ); ?></code>
									<?php endif; ?>
								</p>
							</td>
						</tr>

						<tr>
							<th scope="row">
								<label for="pkcore_alert_volume_floor"><?php esc_html_e( 'Minimum delivered volume', 'publisherkits-socialpromoter' ); ?></label>
							</th>
							<td>
								<input type="number" min="0" step="1" class="small-text"
									name="pkcore_alert_volume_floor" id="pkcore_alert_volume_floor"
									value="<?php echo esc_attr( (string) PKCORE_Alerts::volume_floor() ); ?>" />
								<p class="description">
									<?php esc_html_e( 'Per-send rules stay silent below this many delivered messages. Small sends swing wildly in percentage terms and would generate constant noise. Rules that are not per-send, such as DMARC alignment, apply their own floors.', 'publisherkits-socialpromoter' ); ?>
								</p>
							</td>
						</tr>

						<tr>
							<th scope="row">
								<label for="pkcore_alert_cooldown_hours"><?php esc_html_e( 'Cool-down', 'publisherkits-socialpromoter' ); ?></label>
							</th>
							<td>
								<input type="number" min="0" step="1" class="small-text"
									name="pkcore_alert_cooldown_hours" id="pkcore_alert_cooldown_hours"
									value="<?php echo esc_attr( (string) PKCORE_Alerts::cooldown_hours() ); ?>" />
								<?php esc_html_e( 'hours', 'publisherkits-socialpromoter' ); ?>
								<p class="description">
									<?php esc_html_e( 'How long before the same signal may email again for the same site. A four-week outage should produce two or three emails, not thirty. Correlated rules share one cool-down, so one incident sends one message. Zero disables the cool-down.', 'publisherkits-socialpromoter' ); ?>
								</p>
							</td>
						</tr>

						<tr>
							<th scope="row"><?php esc_html_e( 'Email delivery', 'publisherkits-socialpromoter' ); ?></th>
							<td>
								<label>
									<input type="checkbox" name="pkcore_alert_emails_enabled" value="1"
										<?php checked( PKCORE_Alerts::emails_enabled() ); ?> />
									<?php esc_html_e( 'Send alert emails', 'publisherkits-socialpromoter' ); ?>
								</label>
								<p class="description">
									<?php esc_html_e( 'Turning this off does not stop detection. Alerts are still recorded, marked as suppressed, so the log stays complete and it remains obvious afterwards that sending was off.', 'publisherkits-socialpromoter' ); ?>
								</p>
							</td>
						</tr>
					</table>

					<?php submit_button( __( 'Save alert settings', 'publisherkits-socialpromoter' ) ); ?>
				</form>

				<hr />

				<h2><?php esc_html_e( 'Check the path works', 'publisherkits-socialpromoter' ); ?></h2>
				<p class="description" style="max-width:46em;">
					<?php esc_html_e( 'Sends one test alert to the addresses above, using exactly the path a real alert takes. Worth doing on every site: a rule that detects correctly and cannot send is indistinguishable from no rule at all. The test bypasses the cool-down and is recorded in the log like any other alert.', 'publisherkits-socialpromoter' ); ?>
				</p>
				<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
					<input type="hidden" name="action" value="pkcore_test_alert" />
					<?php wp_nonce_field( 'pkcore_test_alert', 'pkcore_test_nonce' ); ?>
					<?php submit_button( __( 'Send a test alert', 'publisherkits-socialpromoter' ), 'secondary', 'submit', false ); ?>
				</form>
			</div>
			<?php
		}

		/**
		 * Print and clear the stored save/test notice.
		 *
		 * @return void
		 */
		private static function render_notice() {
			$notice = get_transient( self::NOTICE_TRANSIENT );
			if ( empty( $notice ) || ! is_array( $notice ) ) {
				return;
			}
			delete_transient( self::NOTICE_TRANSIENT );

			$class = ( 'error' === ( $notice['type'] ?? '' ) ) ? 'notice-error' : 'notice-success';
			printf(
				'<div class="notice %1$s is-dismissible"><p>%2$s</p></div>',
				esc_attr( $class ),
				esc_html( (string) ( $notice['message'] ?? '' ) )
			);
		}

		// ====== Handlers ======

		/**
		 * Validate and save the settings.
		 *
		 * @return void
		 */
		public static function handle_save() {
			self::guard( 'pkcore_save_alerts', 'pkcore_alerts_nonce' );

			// Nonce and capability are verified in self::guard() above; the
			// sniffer cannot follow that indirection, so the $_POST reads in
			// this block carry explicit ignores. Each value is validated on
			// the way in: the recipient list through sanitize_textarea_field
			// plus PKCORE_Alerts::parse_recipients, the numbers by integer cast.
			// phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in self::guard().
			$raw = isset( $_POST[ PKCORE_Alerts::OPT_RECIPIENTS ] )
				? sanitize_textarea_field( wp_unslash( $_POST[ PKCORE_Alerts::OPT_RECIPIENTS ] ) )
				: '';

			$valid   = PKCORE_Alerts::parse_recipients( $raw );
			$invalid = PKCORE_Alerts::invalid_recipients( $raw );

			// Store the normalised, validated list rather than the raw input.
			// What is displayed then always matches what will actually be
			// emailed - a settings screen that shows an address the notifier
			// silently drops is its own small version of this whole problem.
			update_option( PKCORE_Alerts::OPT_RECIPIENTS, implode( ', ', $valid ), false );

			$floor = isset( $_POST[ PKCORE_Alerts::OPT_VOLUME_FLOOR ] )
				? max( 0, (int) wp_unslash( $_POST[ PKCORE_Alerts::OPT_VOLUME_FLOOR ] ) ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- integer cast sanitises.
				: 1000;
			update_option( PKCORE_Alerts::OPT_VOLUME_FLOOR, $floor, false );

			$cooldown = isset( $_POST[ PKCORE_Alerts::OPT_COOLDOWN_HOURS ] )
				? max( 0, (int) wp_unslash( $_POST[ PKCORE_Alerts::OPT_COOLDOWN_HOURS ] ) ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- integer cast sanitises.
				: 48;
			update_option( PKCORE_Alerts::OPT_COOLDOWN_HOURS, $cooldown, false );

			update_option(
				PKCORE_Alerts::OPT_ENABLED,
				isset( $_POST[ PKCORE_Alerts::OPT_ENABLED ] ) ? '1' : '0',
				false
			);
			// phpcs:enable WordPress.Security.NonceVerification.Missing

			if ( ! empty( $invalid ) ) {
				self::notice(
					'error',
					sprintf(
						/* translators: %s: comma-separated list of rejected entries. */
						__( 'Saved, but these were not valid email addresses and have been removed: %s', 'publisherkits-socialpromoter' ),
						implode( ', ', $invalid )
					)
				);
			} elseif ( empty( $valid ) ) {
				self::notice(
					'success',
					sprintf(
						/* translators: %s: the site administrator email address. */
						__( 'Saved. With no recipients set, alerts go to the site administrator: %s', 'publisherkits-socialpromoter' ),
						(string) get_option( 'admin_email', '' )
					)
				);
			} else {
				self::notice( 'success', __( 'Alert settings saved.', 'publisherkits-socialpromoter' ) );
			}

			self::redirect_back();
		}

		/**
		 * Raise a test alert down the real delivery path.
		 *
		 * @return void
		 */
		public static function handle_test() {
			self::guard( 'pkcore_test_alert', 'pkcore_test_nonce' );

			$recipients = PKCORE_Alerts::recipients();

			if ( empty( $recipients ) ) {
				self::notice( 'error', __( 'No valid recipient address is configured, and the site administrator address is not usable either. Nothing was sent.', 'publisherkits-socialpromoter' ) );
				self::redirect_back();
			}

			// A unique code per test so the cool-down never suppresses one,
			// and so tests are distinguishable from real signals in the log.
			$id = pk_signal_alert(
				array(
					'code'     => 'pkcore.test_alert_' . gmdate( 'YmdHis' ),
					'group'    => 'pkcore_test_alert_' . gmdate( 'YmdHis' ),
					'severity' => 'critical',
					'title'    => __( 'Test alert - the alerting path is working', 'publisherkits-socialpromoter' ),
					'detail'   => __( 'You asked for this from PublisherKits > Alerts. Nothing is wrong. If this message arrived, detection rules on this site can reach you.', 'publisherkits-socialpromoter' ),
					'context'  => array(
						'triggered_by' => wp_get_current_user()->user_login,
						'recipients'   => implode( ', ', $recipients ),
					),
				)
			);

			if ( $id ) {
				self::notice(
					'success',
					sprintf(
						/* translators: %s: comma-separated recipient addresses. */
						__( 'Test alert sent to %s. If it does not arrive, the fault is in this site\'s mail delivery, not in the rules.', 'publisherkits-socialpromoter' ),
						implode( ', ', $recipients )
					)
				);
			} else {
				self::notice( 'error', __( 'The test alert could not be recorded. Check the error log for [PK Alerts] entries.', 'publisherkits-socialpromoter' ) );
			}

			self::redirect_back();
		}

		// ====== Helpers ======

		/**
		 * Capability and nonce check, or die.
		 *
		 * @param string $action Nonce action.
		 * @param string $field  Nonce field name.
		 * @return void
		 */
		private static function guard( $action, $field ) {
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_die( esc_html__( 'Permission denied.', 'publisherkits-socialpromoter' ) );
			}

			$nonce = isset( $_POST[ $field ] ) ? sanitize_text_field( wp_unslash( $_POST[ $field ] ) ) : '';
			if ( ! wp_verify_nonce( $nonce, $action ) ) {
				wp_die( esc_html__( 'Security check failed.', 'publisherkits-socialpromoter' ) );
			}
		}

		/**
		 * Store a notice for the next page load.
		 *
		 * @param string $type    'success' or 'error'.
		 * @param string $message Message.
		 * @return void
		 */
		private static function notice( $type, $message ) {
			set_transient(
				self::NOTICE_TRANSIENT,
				array(
					'type'    => $type,
					'message' => $message,
				),
				60
			);
		}

		/**
		 * Return to the Alerts screen.
		 *
		 * @return void
		 */
		private static function redirect_back() {
			wp_safe_redirect( admin_url( 'admin.php?page=' . self::SLUG ) );
			exit;
		}
	}
}
