<?php
/**
 * Admin UI: configure the service, pick pages, capture a baseline, run a manual
 * check, and view the latest report.
 *
 * @package UpdateGuardian
 */

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

/**
 * Settings screen and its form/action handling.
 */
class Upguard_Settings {

	/**
	 * @var Upguard_Client
	 */
	private $client;

	/**
	 * @param Upguard_Client $client Detection API client.
	 */
	public function __construct( Upguard_Client $client ) {
		$this->client = $client;
	}

	/**
	 * Register hooks.
	 */
	public function hooks() {
		add_action( 'admin_menu', array( $this, 'add_page' ) );
		add_action( 'admin_init', array( $this, 'register_settings' ) );
		add_action( 'admin_post_upguard_baseline', array( $this, 'handle_baseline' ) );
		add_action( 'admin_post_upguard_check', array( $this, 'handle_check' ) );
	}

	/**
	 * Add the settings page under Tools.
	 */
	public function add_page() {
		add_management_page(
			__( 'Update Guardian', 'sumeetsarna-update-guardian' ),
			__( 'Update Guardian', 'sumeetsarna-update-guardian' ),
			'manage_options',
			'update-guardian',
			array( $this, 'render' )
		);
	}

	/**
	 * Register the stored options.
	 */
	public function register_settings() {
		register_setting( 'upguard', Upguard_Client::OPT_API_URL, array( 'sanitize_callback' => 'esc_url_raw' ) );
		register_setting( 'upguard', Upguard_Client::OPT_LICENSE, array( 'sanitize_callback' => 'sanitize_text_field' ) );
		register_setting( 'upguard', 'upguard_paths', array( 'sanitize_callback' => array( $this, 'sanitize_paths' ) ) );
	}

	/**
	 * Sanitize the monitored-paths textarea: one relative path per line.
	 *
	 * Each line is sanitized with sanitize_text_field(), then validated as a
	 * site-relative path — leading slash enforced and any character that does
	 * not belong in a URL path or query string stripped.
	 *
	 * @param string $value Raw textarea value.
	 * @return string
	 */
	public function sanitize_paths( $value ) {
		$lines = preg_split( '/\r\n|\r|\n/', (string) $value );
		$clean = array();
		foreach ( $lines as $line ) {
			$line = sanitize_text_field( $line );
			if ( '' === $line ) {
				continue;
			}
			// Site-relative only: force a single leading slash.
			$line = '/' . ltrim( $line, '/\\' );
			// Allow only characters valid in a URL path or query string.
			$line = preg_replace( '#[^A-Za-z0-9\-_.~%/?&=+]#', '', $line );
			// Collapse any repeated leading slashes left after stripping.
			$line = '/' . ltrim( $line, '/' );
			if ( ! in_array( $line, $clean, true ) ) {
				$clean[] = $line;
			}
		}
		return implode( "\n", $clean );
	}

	/**
	 * Capture a fresh baseline, then bounce back with a status flag.
	 */
	public function handle_baseline() {
		$this->guard( 'upguard_baseline' );
		$result = $this->client->capture_baseline();
		$this->redirect_with( is_wp_error( $result ) ? 'baseline_error' : 'baseline_ok', $result );
	}

	/**
	 * Run a manual check now and store the report.
	 */
	public function handle_check() {
		$this->guard( 'upguard_check' );
		$report  = $this->client->compare();
		$monitor = new Upguard_Monitor( $this->client );
		$monitor->store_report( $report );
		$this->redirect_with( is_wp_error( $report ) ? 'check_error' : 'check_ok', $report );
	}

	/**
	 * Verify capability + nonce for a POST action.
	 *
	 * @param string $action Nonce action name.
	 */
	private function guard( $action ) {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Not allowed.', 'sumeetsarna-update-guardian' ) );
		}
		check_admin_referer( $action );
	}

	/**
	 * Redirect back to the settings page carrying a status message.
	 *
	 * @param string         $status Status slug.
	 * @param array|WP_Error $result Result to extract an error message from.
	 */
	private function redirect_with( $status, $result ) {
		$args = array(
			'page'          => 'update-guardian',
			'upguard_status' => $status,
		);
		if ( is_wp_error( $result ) ) {
			// add_query_arg() URL-encodes the value; no manual encoding needed.
			$args['upguard_msg'] = $result->get_error_message();
		}
		wp_safe_redirect( add_query_arg( $args, admin_url( 'tools.php' ) ) );
		exit;
	}

	/**
	 * Render the settings page.
	 */
	public function render() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		$this->render_notice();
		?>
		<div class="wrap">
			<h1><?php esc_html_e( 'Update Guardian', 'sumeetsarna-update-guardian' ); ?></h1>

			<?php $this->render_license_status(); ?>

			<form method="post" action="options.php">
				<?php settings_fields( 'upguard' ); ?>
				<table class="form-table" role="presentation">
					<tr>
						<th scope="row"><label for="upguard_api_url"><?php esc_html_e( 'Service URL', 'sumeetsarna-update-guardian' ); ?></label></th>
						<td>
							<input name="<?php echo esc_attr( Upguard_Client::OPT_API_URL ); ?>" id="upguard_api_url" type="url" class="regular-text" value="<?php echo esc_attr( get_option( Upguard_Client::OPT_API_URL ) ); ?>" placeholder="<?php echo esc_attr( Upguard_Client::DEFAULT_API_URL ); ?>" />
							<p class="description"><?php esc_html_e( 'Leave empty to use the default Update Guardian service.', 'sumeetsarna-update-guardian' ); ?></p>
						</td>
					</tr>
					<tr>
						<th scope="row"><label for="upguard_license_key"><?php esc_html_e( 'Service token (fallback)', 'sumeetsarna-update-guardian' ); ?></label></th>
						<td>
							<input name="<?php echo esc_attr( Upguard_Client::OPT_LICENSE ); ?>" id="upguard_license_key" type="text" class="regular-text" value="<?php echo esc_attr( get_option( Upguard_Client::OPT_LICENSE ) ); ?>" />
							<p class="description"><?php esc_html_e( 'With an active Pro license this is set automatically. Only needed for local development before activation.', 'sumeetsarna-update-guardian' ); ?></p>
						</td>
					</tr>
					<tr>
						<th scope="row"><label for="upguard_paths"><?php esc_html_e( 'Pages to monitor', 'sumeetsarna-update-guardian' ); ?></label></th>
						<td>
							<textarea name="upguard_paths" id="upguard_paths" rows="6" class="large-text code" placeholder="/&#10;/shop/&#10;/cart/&#10;/checkout/"><?php echo esc_textarea( get_option( 'upguard_paths', "/\n" ) ); ?></textarea>
							<p class="description"><?php esc_html_e( 'One path per line, relative to your site root.', 'sumeetsarna-update-guardian' ); ?></p>
						</td>
					</tr>
				</table>
				<?php submit_button( __( 'Save settings', 'sumeetsarna-update-guardian' ) ); ?>
			</form>

			<hr />

			<h2><?php esc_html_e( 'Actions', 'sumeetsarna-update-guardian' ); ?></h2>
			<p><?php esc_html_e( 'Capture a baseline while your site is healthy. After each update, Update Guardian compares against it automatically.', 'sumeetsarna-update-guardian' ); ?></p>
			<div style="display:flex; gap:8px;">
				<?php $this->action_form( 'upguard_baseline', __( 'Capture baseline now', 'sumeetsarna-update-guardian' ), 'primary' ); ?>
				<?php $this->action_form( 'upguard_check', __( 'Run check now', 'sumeetsarna-update-guardian' ), 'secondary' ); ?>
			</div>

			<?php $this->render_report(); ?>
		</div>
		<?php
	}

	/**
	 * Output a small self-submitting action form.
	 *
	 * @param string $action Nonce/action name.
	 * @param string $label  Button label.
	 * @param string $type   Button style.
	 */
	private function action_form( $action, $label, $type ) {
		?>
		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="<?php echo esc_attr( $action ); ?>" />
			<?php wp_nonce_field( $action ); ?>
			<?php submit_button( $label, $type, 'submit', false ); ?>
		</form>
		<?php
	}

	/**
	 * Show a success/error notice based on the redirect status.
	 */
	private function render_notice() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only status flag for a notice; the action that set it was already nonce-verified.
		$status = isset( $_GET['upguard_status'] ) ? sanitize_key( wp_unslash( $_GET['upguard_status'] ) ) : '';
		if ( '' === $status ) {
			return;
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only status message for display only.
		$msg = isset( $_GET['upguard_msg'] ) ? sanitize_text_field( wp_unslash( $_GET['upguard_msg'] ) ) : '';

		$map = array(
			'baseline_ok'    => array( 'success', __( 'Baseline captured.', 'sumeetsarna-update-guardian' ) ),
			'baseline_error' => array( 'error', __( 'Could not capture baseline: ', 'sumeetsarna-update-guardian' ) . $msg ),
			'check_ok'       => array( 'success', __( 'Check complete. See the report below.', 'sumeetsarna-update-guardian' ) ),
			'check_error'    => array( 'error', __( 'Check failed: ', 'sumeetsarna-update-guardian' ) . $msg ),
		);
		if ( ! isset( $map[ $status ] ) ) {
			return;
		}
		printf(
			'<div class="notice notice-%s is-dismissible"><p>%s</p></div>',
			esc_attr( $map[ $status ][0] ),
			esc_html( $map[ $status ][1] )
		);
	}

	/**
	 * Show which service tier this site is on, with a link to manage or upgrade.
	 * All plugin functionality is identical on every tier — plans only affect
	 * how many pages the external service analyzes per check.
	 */
	private function render_license_status() {
		if ( ! function_exists( 'upguard_fs' ) ) {
			return;
		}
		if ( upguard_is_pro() ) {
			printf(
				'<div class="notice notice-success inline"><p>%s <a href="%s">%s</a></p></div>',
				esc_html__( 'Pro service plan active — the service analyzes all your monitored pages on every check.', 'sumeetsarna-update-guardian' ),
				esc_url( upguard_fs()->get_account_url() ),
				esc_html__( 'Manage license', 'sumeetsarna-update-guardian' )
			);
		} else {
			printf(
				'<div class="notice notice-info inline"><p>%s <a href="%s">%s</a></p></div>',
				esc_html__( 'Using the free service tier: the service analyzes up to 3 pages per check. Upgrade for unlimited pages.', 'sumeetsarna-update-guardian' ),
				esc_url( upguard_fs()->get_upgrade_url() ),
				esc_html__( 'Upgrade', 'sumeetsarna-update-guardian' )
			);
		}
	}

	/**
	 * Render the latest stored report.
	 */
	private function render_report() {
		$report = Upguard_Monitor::last_report();
		if ( ! $report ) {
			return;
		}
		echo '<h2>' . esc_html__( 'Latest report', 'sumeetsarna-update-guardian' ) . '</h2>';
		// translators: %s is the date and time the check was run.
		echo '<p>' . esc_html( sprintf( __( 'Run: %s', 'sumeetsarna-update-guardian' ), wp_date( 'Y-m-d H:i', $report['time'] ) ) ) . '</p>';

		if ( isset( $report['error'] ) ) {
			echo '<div class="notice notice-error inline"><p>' . esc_html( $report['error'] ) . '</p></div>';
			return;
		}

		$regressions = isset( $report['regressions'] ) ? (int) $report['regressions'] : 0;
		if ( 0 === $regressions ) {
			echo '<div class="notice notice-success inline"><p>' . esc_html__( 'No regressions detected.', 'sumeetsarna-update-guardian' ) . '</p></div>';
		} else {
			// translators: %d is the number of pages that regressed.
			echo '<div class="notice notice-error inline"><p>' . esc_html( sprintf( _n( '%d page regressed.', '%d pages regressed.', $regressions, 'sumeetsarna-update-guardian' ), $regressions ) ) . '</p></div>';
		}

		echo '<table class="widefat striped"><thead><tr><th>' . esc_html__( 'Page', 'sumeetsarna-update-guardian' ) . '</th><th>' . esc_html__( 'Status', 'sumeetsarna-update-guardian' ) . '</th></tr></thead><tbody>';
		foreach ( (array) $report['pages'] as $page ) {
			$ok    = ! empty( $page['ok'] );
			$detail = $ok ? esc_html__( 'OK', 'sumeetsarna-update-guardian' ) : $this->format_problems( $page['problems'] );
			printf(
				'<tr><td><code>%s</code></td><td>%s %s</td></tr>',
				esc_html( $page['path'] ),
				$ok ? '&#9989;' : '&#10060;',
				wp_kses_post( $detail )
			);
		}
		echo '</tbody></table>';
	}

	/**
	 * Turn a page's problem list into readable HTML.
	 *
	 * @param array $problems List of {type, detail}.
	 * @return string
	 */
	private function format_problems( $problems ) {
		$labels = array(
			'unreachable'     => __( 'Page unreachable', 'sumeetsarna-update-guardian' ),
			'http_error'      => __( 'HTTP error', 'sumeetsarna-update-guardian' ),
			'php_fatal'       => __( 'PHP fatal error', 'sumeetsarna-update-guardian' ),
			'console_error'   => __( 'JavaScript errors', 'sumeetsarna-update-guardian' ),
			'missing_element' => __( 'Missing feature', 'sumeetsarna-update-guardian' ),
			'size_change'     => __( 'Layout size changed', 'sumeetsarna-update-guardian' ),
			'visual_change'   => __( 'Visual change', 'sumeetsarna-update-guardian' ),
		);
		$out = array();
		foreach ( (array) $problems as $p ) {
			$label = isset( $labels[ $p['type'] ] ) ? $labels[ $p['type'] ] : $p['type'];
			$out[] = esc_html( $label ) . ' <em>(' . esc_html( $p['detail'] ) . ')</em>';
		}
		return implode( '<br />', $out );
	}
}
