<?php
/**
 * Frontend Redirect Handler
 *
 * Handles per-post and per-term 301/302 redirects.
 * Fires before any output is sent to the browser.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Frontend;

use SEOvaultSEO\Core\PostMeta;
use SEOvaultSEO\Compatibility\Detector;
use SEOvaultSEO\RedirectManager\RedirectDb;

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

class Redirect {

	/**
	 * Initialize redirect handling.
	 */
	public static function init() {
		if ( ! Detector::should_output_seo() ) {
			return;
		}

		// Run early — before headers are sent and before other SEO output.
		add_action( 'template_redirect', array( __CLASS__, 'maybe_redirect' ), 0 );
	}

	/**
	 * Check if the current request should be redirected, and do so.
	 */
	public static function maybe_redirect() {
		// Don't redirect in admin, REST, AJAX, or feeds.
		if ( is_admin() || wp_is_json_request() || wp_doing_ajax() || is_feed() ) {
			return;
		}

		// Don't redirect if another SEO plugin is active.
		if ( ! Detector::should_output_seo() ) {
			return;
		}

		// Check 404 monitor redirects first (applies to 404 pages).
		if ( is_404() ) {
			self::maybe_redirect_404();
		}

		// Single post/page redirect.
		if ( is_singular() ) {
			$post_id = get_queried_object_id();

			if ( ! $post_id ) {
				return;
			}

			$redirect_url = PostMeta::get_redirect_url( $post_id );

			if ( '' === $redirect_url ) {
				return;
			}

			self::do_redirect( $post_id, $redirect_url );
		}

		// Term archive redirect.
		if ( is_category() || is_tag() || is_tax() ) {
			$term_id = get_queried_object_id();

			if ( ! $term_id ) {
				return;
			}

			$redirect_url = get_term_meta( $term_id, '_seovault_redirect_url', true );

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

			$redirect_type = (int) get_term_meta( $term_id, '_seovault_redirect_type', true );
			$redirect_type = in_array( $redirect_type, array( 301, 302 ), true ) ? $redirect_type : 301;

			self::do_redirect( $term_id, $redirect_url, $redirect_type, false );
		}
	}

	/**
	 * Perform the redirect.
	 *
	 * @param int    $object_id    Post or term ID.
	 * @param string $redirect_url Target URL.
	 * @param int    $type         Redirect type (301 or 302). Defaults to post meta value.
	 * @param bool   $is_post      Whether this is a post (vs term).
	 */
	private static function do_redirect( $object_id, $redirect_url, $type = null, $is_post = true ) {
		if ( $is_post && null === $type ) {
			$type = PostMeta::get_redirect_type( $object_id );
		}
		$type = in_array( (int) $type, array( 301, 302 ), true ) ? (int) $type : 301;

		// Allow extensions to block or modify the redirect.
		$redirect_url = apply_filters( 'seovaultseo_redirect_url', $redirect_url, $object_id, $type );
		$type         = (int) apply_filters( 'seovaultseo_redirect_type', $type, $object_id, $redirect_url );

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

		// Block external redirects if configured to do so.
		$allow_external = apply_filters( 'seovaultseo_allow_external_redirects', true );

		if ( ! $allow_external && self::is_external_url( $redirect_url ) ) {
			return;
		}

		// Fire action before redirect so extensions can observe or modify the target URL.
		do_action( 'seovaultseo_before_redirect', $object_id, $redirect_url, $type );

		// Sanitize the URL.
		$redirect_url = esc_url_raw( $redirect_url );

		if ( '' === $redirect_url ) {
			return;
		}

		self::send_redirect( $redirect_url, $type );
	}

	/**
	 * Issue the HTTP redirect.
	 *
	 * Uses wp_redirect() for allowed external targets (wp_safe_redirect only allows
	 * hosts in wp_allowed_redirect_hosts). Internal targets keep wp_safe_redirect().
	 *
	 * @param string $url  Target URL (already sanitized).
	 * @param int    $type HTTP status.
	 * @return void
	 */
	private static function send_redirect( $url, $type ) {
		if ( self::is_external_url( $url ) ) {
			// phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- External targets are gated by seovaultseo_allow_external_redirects + admin-configured rules.
			wp_redirect( $url, $type );
		} else {
			wp_safe_redirect( $url, $type );
		}
		exit;
	}

	/**
	 * Check if the current 404 request matches a saved 404 redirect.
	 */
	private static function maybe_redirect_404() {
		$home_path = wp_parse_url( home_url( '/' ), PHP_URL_PATH ) ?? '';
		$uri       = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
		$uri       = str_replace( $home_path, '', $uri );
		$uri       = untrailingslashit( ltrim( $uri, '/' ) );

		// Check the redirect manager database table first.
		$redirect = RedirectDb::find_match( $uri );
		if ( $redirect ) {
			$type = $redirect->get_type();
			$url  = $redirect->get_target();

			// For 410 and 451, there may be no target URL — just send the status code.
			if ( in_array( $type, array( 410, 451 ), true ) ) {
				status_header( $type );
				exit;
			}

			if ( ! empty( $url ) ) {
				$url = esc_url_raw( $url );
				if ( '' === $url ) {
					return;
				}
				$allow_external = apply_filters( 'seovaultseo_allow_external_redirects', true );
				if ( ! $allow_external && self::is_external_url( $url ) ) {
					return;
				}
				self::send_redirect( $url, $type );
			}
		}

		// Fall back to legacy option-based 404 redirects.
		$redirects = get_option( 'seovaultseo_404_redirects', array() );
		if ( empty( $redirects ) || ! is_array( $redirects ) ) {
			return;
		}

		if ( isset( $redirects[ $uri ] ) ) {
			$entry = $redirects[ $uri ];
			$url   = $entry['url'] ?? '';
			$type  = isset( $entry['type'] ) ? (int) $entry['type'] : 301;

			if ( ! empty( $url ) ) {
				$url = esc_url_raw( $url );
				if ( '' === $url ) {
					return;
				}
				$allow_external = apply_filters( 'seovaultseo_allow_external_redirects', true );
				if ( ! $allow_external && self::is_external_url( $url ) ) {
					return;
				}
				self::send_redirect( $url, $type );
			}
		}
	}

	/**
	 * Check if a URL is external (different domain than the site).
	 *
	 * @param string $url URL to check.
	 * @return bool
	 */
	private static function is_external_url( $url ) {
		$site_host = wp_parse_url( home_url(), PHP_URL_HOST );
		$url_host  = wp_parse_url( $url, PHP_URL_HOST );

		if ( ! $url_host ) {
			return false;
		}

		return $url_host !== $site_host;
	}
}
