<?php
/**
 * Redirect Validator
 *
 * Validates redirect entries before they are saved to the database.
 *
 * @package SEOvaultSEO\RedirectManager
 */

namespace SEOvaultSEO\RedirectManager;

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

/**
 * Validates redirect objects for correctness and safety.
 */
class RedirectValidator {

	/**
	 * The last validation error message.
	 *
	 * @var string|null
	 */
	protected $error = null;

	/**
	 * Validate a redirect before creating it.
	 *
	 * @param Redirect $redirect The redirect to validate.
	 * @return bool True if valid, false otherwise.
	 */
	public function validate_create( Redirect $redirect ) {
		$this->error = null;

		if ( ! $this->check_origin_present( $redirect ) ) {
			return false;
		}

		if ( ! $this->check_target_present( $redirect ) ) {
			return false;
		}

		if ( ! $this->check_control_chars( $redirect ) ) {
			return false;
		}

		if ( ! $this->check_self_redirect( $redirect ) ) {
			return false;
		}

		if ( ! $this->check_uniqueness( $redirect, null ) ) {
			return false;
		}

		if ( ! $this->check_regex_validity( $redirect ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Validate a redirect before updating it.
	 *
	 * @param Redirect $redirect     The new redirect data.
	 * @param int      $existing_id  The ID of the redirect being updated.
	 * @return bool True if valid, false otherwise.
	 */
	public function validate_update( Redirect $redirect, $existing_id ) {
		$this->error = null;

		if ( ! $this->check_origin_present( $redirect ) ) {
			return false;
		}

		if ( ! $this->check_target_present( $redirect ) ) {
			return false;
		}

		if ( ! $this->check_control_chars( $redirect ) ) {
			return false;
		}

		if ( ! $this->check_self_redirect( $redirect ) ) {
			return false;
		}

		if ( ! $this->check_uniqueness( $redirect, $existing_id ) ) {
			return false;
		}

		if ( ! $this->check_regex_validity( $redirect ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Get the last validation error message.
	 *
	 * @return string|null
	 */
	public function get_error() {
		return $this->error;
	}

	/**
	 * Check that the origin is not empty.
	 *
	 * @param Redirect $redirect Redirect to check.
	 * @return bool
	 */
	protected function check_origin_present( Redirect $redirect ) {
		if ( '' === trim( $redirect->get_origin() ) ) {
			$this->error = __( 'The redirect origin (source URL) cannot be empty.', 'seovault-ai' );
			return false;
		}
		return true;
	}

	/**
	 * Check that the target is not empty (except for 410/451 types).
	 *
	 * @param Redirect $redirect Redirect to check.
	 * @return bool
	 */
	protected function check_target_present( Redirect $redirect ) {
		if ( in_array( $redirect->get_type(), array( Redirect::TYPE_DELETED, Redirect::TYPE_UNAVAILABLE ), true ) ) {
			return true;
		}

		if ( '' === trim( $redirect->get_target() ) ) {
			$this->error = __( 'The redirect target (destination URL) cannot be empty.', 'seovault-ai' );
			return false;
		}
		return true;
	}

	/**
	 * Check that origin and target do not contain control characters.
	 *
	 * @param Redirect $redirect Redirect to check.
	 * @return bool
	 */
	protected function check_control_chars( Redirect $redirect ) {
		if ( Redirect::contains_control_chars( $redirect->get_origin() ) ) {
			$this->error = __( 'The redirect origin contains invalid control characters.', 'seovault-ai' );
			return false;
		}

		if ( Redirect::contains_control_chars( $redirect->get_target() ) ) {
			$this->error = __( 'The redirect target contains invalid control characters.', 'seovault-ai' );
			return false;
		}

		return true;
	}

	/**
	 * Check that origin and target are not the same (self-redirect).
	 * Only applies to plain redirects.
	 *
	 * @param Redirect $redirect Redirect to check.
	 * @return bool
	 */
	protected function check_self_redirect( Redirect $redirect ) {
		if ( Redirect::FORMAT_REGEX === $redirect->get_format() ) {
			return true;
		}

		if ( $redirect->get_origin() === $redirect->get_target() ) {
			$this->error = __( 'The redirect origin and target cannot be the same URL.', 'seovault-ai' );
			return false;
		}

		return true;
	}

	/**
	 * Check that the origin+format combination is unique.
	 *
	 * @param Redirect $redirect     Redirect to check.
	 * @param int|null $exclude_id   ID to exclude (for updates).
	 * @return bool
	 */
	protected function check_uniqueness( Redirect $redirect, $exclude_id = null ) {
		if ( RedirectDb::exists( $redirect->get_origin(), $redirect->get_format(), $exclude_id ) ) {
			$this->error = __( 'A redirect with this origin already exists.', 'seovault-ai' );
			return false;
		}

		return true;
	}

	/**
	 * For regex redirects, check that the pattern is a valid regex.
	 *
	 * @param Redirect $redirect Redirect to check.
	 * @return bool
	 */
	protected function check_regex_validity( Redirect $redirect ) {
		if ( Redirect::FORMAT_REGEX !== $redirect->get_format() ) {
			return true;
		}

		// Suppress errors and check if the regex is valid.
		$result = @preg_match( '#' . $redirect->get_origin() . '#i', '' );

		if ( false === $result ) {
			$this->error = __( 'The regex pattern is invalid.', 'seovault-ai' );
			return false;
		}

		return true;
	}
}
