<?php
/**
 * Livada SEO — 404 Monitor
 *
 * Logs every front-end 404 hit (path, hits, last_seen, referer) to a dedicated
 * table, then surfaces them in the admin so the user can convert frequent 404s
 * into proper 301 redirects with a single click.
 *
 * Logging is throttled with a per-path object cache key (60 s) so a flood of
 * bot hits cannot spam the DB; only one INSERT/UPDATE fires per minute per
 * path per request lifecycle.
 *
 * @package Livada_SEO
 */

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

class Livada_SEO_404_Monitor {

	/** Object-cache TTL for the per-path throttle. */
	const THROTTLE_TTL = 60;

	/** Maximum path length stored. */
	const MAX_PATH_LEN = 500;

	// ── Bootstrap ─────────────────────────────────────────────────────────────

	public static function init(): void {
		add_action( 'template_redirect', [ self::class, 'maybe_log' ], 999 );

		// Admin sub-page.
		add_action( 'admin_menu', [ self::class, 'register_menu' ], 25 );

		// AJAX: convert a 404 row to a redirect, or delete a 404 row.
		add_action( 'wp_ajax_livada_seo_404_to_redirect', [ self::class, 'ajax_to_redirect' ] );
		add_action( 'wp_ajax_livada_seo_404_delete',      [ self::class, 'ajax_delete'      ] );

		// Ensure the table exists (cheap idempotent check).
		add_action( 'admin_init', [ self::class, 'maybe_create_table' ] );
	}

	public static function maybe_create_table(): void {
		global $wpdb;
		$table   = $wpdb->prefix . 'livada_seo_404_log';
		$option  = 'livada_seo_404_table_version';
		$current = (string) get_option( $option, '' );

		if ( '1.0' === $current ) {
			return;
		}

		$charset = $wpdb->get_charset_collate();
		$sql = "CREATE TABLE {$table} (
			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
			path VARCHAR(500) NOT NULL,
			hits INT UNSIGNED NOT NULL DEFAULT 1,
			last_seen DATETIME NOT NULL,
			referer VARCHAR(500) NULL,
			user_agent VARCHAR(255) NULL,
			PRIMARY KEY (id),
			UNIQUE KEY path (path(191)),
			KEY hits (hits)
		) {$charset};";

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( $sql );

		update_option( $option, '1.0', false );
	}

	// ── Logging ───────────────────────────────────────────────────────────────

	public static function maybe_log(): void {
		if ( ! is_404() || is_robots() || is_feed() || is_admin() ) {
			return;
		}

		// Skip favicon and obvious bot artefacts.
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$raw = isset( $_SERVER['REQUEST_URI'] ) ? (string) $_SERVER['REQUEST_URI'] : '';
		$path = (string) wp_parse_url( $raw, PHP_URL_PATH );

		if ( '' === $path || '/' === $path ) {
			return;
		}

		if ( preg_match( '#\.(ico|png|jpg|jpeg|gif|webp|css|js|map|woff2?)$#i', $path ) ) {
			return;
		}

		$path = substr( $path, 0, self::MAX_PATH_LEN );

		// Throttle per-path via object cache.
		$cache_key = 'livada_seo_404_' . md5( $path );
		if ( wp_cache_get( $cache_key, 'livada_seo_404' ) ) {
			return;
		}
		wp_cache_set( $cache_key, 1, 'livada_seo_404', self::THROTTLE_TTL );

		global $wpdb;
		$table = $wpdb->prefix . 'livada_seo_404_log';

		$referer    = isset( $_SERVER['HTTP_REFERER'] )    ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; // phpcs:ignore
		$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // phpcs:ignore
		$user_agent = substr( $user_agent, 0, 255 );

		// Upsert: try INSERT, fall back to UPDATE on duplicate key.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->query(
			$wpdb->prepare(
				"INSERT INTO {$table} (path, hits, last_seen, referer, user_agent)
				 VALUES (%s, 1, %s, %s, %s)
				 ON DUPLICATE KEY UPDATE
				   hits      = hits + 1,
				   last_seen = VALUES(last_seen),
				   referer   = VALUES(referer),
				   user_agent = VALUES(user_agent)",
				$path,
				current_time( 'mysql' ),
				$referer,
				$user_agent
			)
		);
	}

	// ── Admin sub-page ────────────────────────────────────────────────────────

	public static function register_menu(): void {
		add_submenu_page(
			'livada-seo',
			__( '404 Monitor — Livada SEO', 'livada-seo' ),
			__( '404 Monitor', 'livada-seo' ),
			'manage_options',
			'livada-seo-404',
			[ self::class, 'render_page' ]
		);
	}

	public static function render_page(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'livada-seo' ) );
		}

		global $wpdb;
		$table = $wpdb->prefix . 'livada_seo_404_log';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$rows = $wpdb->get_results(
			"SELECT id, path, hits, last_seen, referer FROM {$table} ORDER BY hits DESC, last_seen DESC LIMIT 200"
		);

		?>
		<div class="wrap livada-seo-wrap">
			<h1><?php esc_html_e( '404 Monitor', 'livada-seo' ); ?></h1>
			<p class="description">
				<?php esc_html_e( 'Top 200 paths returning 404 on the front end. Convert frequent ones into redirects in one click.', 'livada-seo' ); ?>
			</p>

			<table class="wp-list-table widefat fixed striped">
				<thead>
					<tr>
						<th style="width: 50%;"><?php esc_html_e( 'Path', 'livada-seo' ); ?></th>
						<th style="width: 8%;"><?php esc_html_e( 'Hits', 'livada-seo' ); ?></th>
						<th style="width: 14%;"><?php esc_html_e( 'Last seen', 'livada-seo' ); ?></th>
						<th style="width: 28%;"><?php esc_html_e( 'Action', 'livada-seo' ); ?></th>
					</tr>
				</thead>
				<tbody id="livada-404-tbody">
					<?php if ( empty( $rows ) ) : ?>
						<tr><td colspan="4"><?php esc_html_e( 'No 404 hits logged yet.', 'livada-seo' ); ?></td></tr>
					<?php else : ?>
						<?php foreach ( $rows as $r ) : ?>
							<tr data-id="<?php echo (int) $r->id; ?>">
								<td>
									<code><?php echo esc_html( $r->path ); ?></code>
									<?php if ( $r->referer ) : ?>
										<br><small><?php esc_html_e( 'From:', 'livada-seo' ); ?> <?php echo esc_html( $r->referer ); ?></small>
									<?php endif; ?>
								</td>
								<td><?php echo (int) $r->hits; ?></td>
								<td><?php echo esc_html( $r->last_seen ); ?></td>
								<td>
									<input type="text"
										class="livada-404-target"
										placeholder="<?php esc_attr_e( '/new-page or https://…', 'livada-seo' ); ?>"
										style="width: 60%;" />
									<button type="button" class="button button-small livada-404-redirect">
										<?php esc_html_e( 'Redirect', 'livada-seo' ); ?>
									</button>
									<button type="button" class="button-link livada-404-delete" style="color:#b32d2e;">
										<?php esc_html_e( 'Delete', 'livada-seo' ); ?>
									</button>
								</td>
							</tr>
						<?php endforeach; ?>
					<?php endif; ?>
				</tbody>
			</table>
		</div>

		<script>
		(function () {
			var ajaxUrl = (window.livadaSEO && window.livadaSEO.ajaxUrl) || '<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>';
			var nonce   = (window.livadaSEO && window.livadaSEO.nonce)   || '<?php echo esc_js( wp_create_nonce( 'livada_seo_admin_nonce' ) ); ?>';

			function post(action, data) {
				var body = new URLSearchParams(Object.assign({ action: action, nonce: nonce }, data));
				return fetch(ajaxUrl, {
					method: 'POST', credentials: 'same-origin',
					headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
					body: body.toString()
				}).then(function (r) { return r.json(); });
			}

			var tbody = document.getElementById('livada-404-tbody');
			if (!tbody) return;

			tbody.addEventListener('click', function (e) {
				var btn = e.target.closest('button');
				if (!btn) return;
				var row = btn.closest('tr[data-id]');
				if (!row) return;
				var id = parseInt(row.getAttribute('data-id'), 10);

				if (btn.classList.contains('livada-404-redirect')) {
					var input  = row.querySelector('.livada-404-target');
					var target = input ? input.value.trim() : '';
					if (!target) { input && input.focus(); return; }
					btn.disabled = true;
					post('livada_seo_404_to_redirect', { id: id, target: target })
						.then(function (res) {
							if (res.success) { row.remove(); }
							else { alert((res.data && res.data.message) || 'Error'); btn.disabled = false; }
						})
						.catch(function () { btn.disabled = false; });
				} else if (btn.classList.contains('livada-404-delete')) {
					if (!window.confirm('Delete?')) return;
					post('livada_seo_404_delete', { id: id })
						.then(function (res) { if (res.success) { row.remove(); } });
				}
			});
		})();
		</script>
		<?php
	}

	// ── AJAX handlers ─────────────────────────────────────────────────────────

	public static function ajax_to_redirect(): void {
		check_ajax_referer( 'livada_seo_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'livada-seo' ) ], 403 );
		}

		$id     = isset( $_POST['id'] )     ? (int) $_POST['id']                          : 0;
		$target = isset( $_POST['target'] ) ? esc_url_raw( wp_unslash( $_POST['target'] ) ) : '';

		if ( $id <= 0 || '' === $target ) {
			wp_send_json_error( [ 'message' => __( 'Invalid target.', 'livada-seo' ) ], 400 );
		}

		global $wpdb;
		$table = $wpdb->prefix . 'livada_seo_404_log';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT path FROM {$table} WHERE id = %d", $id ) );

		if ( ! $row ) {
			wp_send_json_error( [ 'message' => __( 'Row not found.', 'livada-seo' ) ], 404 );
		}

		if ( ! class_exists( 'Livada_SEO_Redirects' ) ) {
			wp_send_json_error( [ 'message' => __( 'Redirects module unavailable.', 'livada-seo' ) ], 500 );
		}

		$result = Livada_SEO_Redirects::add_redirect( $row->path, $target, 301 );

		if ( false === $result ) {
			wp_send_json_error( [ 'message' => __( 'Could not create redirect (duplicate?).', 'livada-seo' ) ] );
		}

		// Remove the 404 row now that it's been redirected.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->delete( $table, [ 'id' => $id ], [ '%d' ] );

		wp_send_json_success( [ 'redirect_id' => (int) $result ] );
	}

	public static function ajax_delete(): void {
		check_ajax_referer( 'livada_seo_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'livada-seo' ) ], 403 );
		}

		$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
		if ( $id <= 0 ) {
			wp_send_json_error( [ 'message' => __( 'Invalid id.', 'livada-seo' ) ], 400 );
		}

		global $wpdb;
		$table = $wpdb->prefix . 'livada_seo_404_log';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->delete( $table, [ 'id' => $id ], [ '%d' ] );

		wp_send_json_success( [] );
	}
}
