<?php
/**
 * Donation Form Custom Post Type.
 *
 * @package SureDonation
 * @since 0.0.1
 */

namespace SureDonation\Inc\Post_Types;

use SureDonation\Inc\Traits\Get_Instance;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Donation Form Post Type Class.
 *
 * @since 0.0.1
 */
class Donation_Form {
	use Get_Instance;

	/**
	 * Post type slug.
	 *
	 * @since 0.0.1
	 */
	public const POST_TYPE = 'suredonation_form';

	/**
	 * Meta key for linked campaign ID.
	 *
	 * @since 0.0.1
	 */
	public const META_CAMPAIGN_ID = '_suredonation_campaign_id';

	/**
	 * Constructor.
	 *
	 * @since 0.0.1
	 */
	public function __construct() {
		add_action( 'init', [ $this, 'register_post_type' ] );
		add_action( 'init', [ $this, 'register_meta' ] );
		add_filter( 'allowed_block_types_all', [ $this, 'restrict_blocks' ], 10, 2 );
		add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
		add_action( 'save_post_' . self::POST_TYPE, [ $this, 'maybe_set_campaign_from_url' ], 10, 2 );
		add_action( 'save_post_' . self::POST_TYPE, [ $this, 'update_field_slugs' ], 10, 2 );
		add_action( 'save_post_' . self::POST_TYPE, [ $this, 'store_block_config' ], 10, 2 );
	}

	/**
	 * Register the donation form post type.
	 *
	 * @return void
	 * @since 0.0.1
	 */
	public function register_post_type() {
		$labels = [
			'name'                  => _x( 'Donation Forms', 'Post type general name', 'suredonation' ),
			'singular_name'         => _x( 'Donation Form', 'Post type singular name', 'suredonation' ),
			'menu_name'             => _x( 'Donation Forms', 'Admin Menu text', 'suredonation' ),
			'name_admin_bar'        => _x( 'Donation Form', 'Add New on Toolbar', 'suredonation' ),
			'add_new'               => __( 'Add New', 'suredonation' ),
			'add_new_item'          => __( 'Add New Form', 'suredonation' ),
			'new_item'              => __( 'New Form', 'suredonation' ),
			'edit_item'             => __( 'Edit Form', 'suredonation' ),
			'view_item'             => __( 'View Form', 'suredonation' ),
			'all_items'             => __( 'All Forms', 'suredonation' ),
			'search_items'          => __( 'Search Forms', 'suredonation' ),
			'parent_item_colon'     => __( 'Parent Forms:', 'suredonation' ),
			'not_found'             => __( 'No forms found.', 'suredonation' ),
			'not_found_in_trash'    => __( 'No forms found in Trash.', 'suredonation' ),
			'archives'              => _x( 'Form archives', 'The post type archive label used in nav menus.', 'suredonation' ),
			'insert_into_item'      => _x( 'Insert into form', 'Overrides the "Insert into post" phrase.', 'suredonation' ),
			'uploaded_to_this_item' => _x( 'Uploaded to this form', 'Overrides the "Uploaded to this post" phrase.', 'suredonation' ),
			'filter_items_list'     => _x( 'Filter forms list', 'Screen reader text for the filter links heading.', 'suredonation' ),
			'items_list_navigation' => _x( 'Forms list navigation', 'Screen reader text for the pagination heading.', 'suredonation' ),
			'items_list'            => _x( 'Forms list', 'Screen reader text for the items list heading.', 'suredonation' ),
		];

		$args = [
			'labels'             => $labels,
			'description'        => __( 'Donation forms for SureDonation.', 'suredonation' ),
			'public'             => false,
			'publicly_queryable' => false,
			'show_ui'            => true,
			'show_in_menu'       => 'suredonation',
			'query_var'          => false,
			'rewrite'            => false,
			'capability_type'    => 'post',
			'has_archive'        => false,
			'hierarchical'       => false,
			'supports'           => [ 'title', 'editor', 'custom-fields' ],
			'show_in_rest'       => true, // Required for Gutenberg.
			'template'           => $this->get_default_template(),
			'template_lock'      => false,
		];

		register_post_type( self::POST_TYPE, $args );
	}

	/**
	 * Register post meta for the donation form.
	 *
	 * @return void
	 * @since 0.0.1
	 */
	public function register_meta() {
		register_post_meta(
			self::POST_TYPE,
			self::META_CAMPAIGN_ID,
			[
				'type'              => 'integer',
				'description'       => __( 'The ID of the linked campaign.', 'suredonation' ),
				'single'            => true,
				'default'           => 0,
				'show_in_rest'      => true,
				'sanitize_callback' => 'absint',
				'auth_callback'     => static function () {
					return current_user_can( 'edit_posts' );
				},
			]
		);
	}

	/**
	 * Restrict allowed blocks in the donation form editor.
	 *
	 * @param bool|array<string>       $allowed_block_types Array of allowed block types or true for all.
	 * @param \WP_Block_Editor_Context $context             Block editor context.
	 * @return bool|array<string> Array of allowed block types.
	 * @since 0.0.1
	 */
	public function restrict_blocks( $allowed_block_types, $context ) {
		if ( ! isset( $context->post ) || self::POST_TYPE !== $context->post->post_type ) {
			return $allowed_block_types;
		}

		// SureDonation form blocks.
		return [
			'sd/input',
			'sd/email',
			'sd/number',
			'sd/multi-choice',
			'sd/anonymous-donation',
			'sd/payment',
			'sd/donate-button',
		];
	}

	/**
	 * Enqueue editor assets for the donation form editor.
	 *
	 * @return void
	 * @since 0.0.1
	 */
	public function enqueue_editor_assets() {
		$screen = get_current_screen();

		if ( ! $screen || self::POST_TYPE !== $screen->post_type ) {
			return;
		}

		// Enqueue form editor specific styles.
		wp_enqueue_style(
			'suredonation-form-editor',
			SUREDONATION_URL . 'assets/css/form-editor.css',
			[],
			SUREDONATION_VER
		);
	}

	/**
	 * Set campaign ID from URL parameter when creating a new form.
	 *
	 * This handles the case when a form is created via the "Add Form" button
	 * from the campaign page, which passes campaign_id as a URL parameter.
	 *
	 * SECURITY: This method implements defense-in-depth with multiple checks:
	 * 1. Nonce verification via verify_save_post_nonce() (WordPress REST nonce or classic editor nonce)
	 * 2. Capability check: current_user_can('edit_post', $post_id) for the form
	 * 3. Capability check: current_user_can('edit_post', $campaign_id) for the campaign
	 * 4. Validation: Campaign must exist and be the correct post type
	 *
	 * @param int      $post_id Post ID.
	 * @param \WP_Post $post    Post object.
	 * @return void
	 * @since 0.0.1
	 */
	public function maybe_set_campaign_from_url( $post_id, $post ) {
		unset( $post ); // Unused parameter.

		// Skip autosave.
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		// Skip revisions.
		if ( wp_is_post_revision( $post_id ) ) {
			return;
		}

		// Security check 1: Verify user has permission to edit this form.
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		// Security check 2: Verify nonce - handles both block editor (REST API) and classic editor.
		if ( ! self::verify_save_post_nonce( $post_id ) ) {
			return;
		}

		// Only process if campaign_id is not already set.
		$existing_campaign_id = self::get_form_campaign_id( $post_id );
		if ( $existing_campaign_id > 0 ) {
			return;
		}

		// Get campaign_id from URL parameter (from "Add Form" button on campaign pages).
		// Security: Nonce verified above via verify_save_post_nonce(). Authorization verified
		// via capability checks on both form (above) and campaign (below).
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified above via verify_save_post_nonce().
		$campaign_id = isset( $_GET['campaign_id'] ) ? absint( $_GET['campaign_id'] ) : 0;

		if ( $campaign_id > 0 ) {
			// Security check 3: Verify the campaign exists, is the correct type, and user can edit it.
			$campaign = get_post( $campaign_id );
			if ( $campaign instanceof \WP_Post && SUREDONATION_POST_TYPE === $campaign->post_type && current_user_can( 'edit_post', $campaign_id ) ) {
				self::set_form_campaign_id( $post_id, $campaign_id );
			}
		}
	}

	/**
	 * Store block configuration for server-side validation.
	 *
	 * This method extracts and stores payment block configuration (amount type,
	 * fixed amount, minimum amount, etc.) in post meta. This stored configuration
	 * is used during payment processing to validate that the submitted amount
	 * matches the form's configured values, preventing payment manipulation attacks.
	 *
	 * @param int      $post_id Post ID.
	 * @param \WP_Post $post    Post object.
	 * @return void
	 * @since 0.0.1
	 */
	public function store_block_config( $post_id, $post ) {
		// Skip autosave.
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		// Skip revisions.
		if ( wp_is_post_revision( $post_id ) ) {
			return;
		}

		// Verify user has permission to edit this post.
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		// Verify nonce - handles both block editor (REST API) and classic editor.
		if ( ! self::verify_save_post_nonce( $post_id ) ) {
			return;
		}

		// Parse blocks from post content.
		$blocks = parse_blocks( $post->post_content );

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

		// Store block configuration using Field_Validation class.
		\SureDonation\Inc\Field_Validation::add_block_config( $blocks, $post_id );
	}

	/**
	 * Generate unique slugs for SureDonation blocks on form save.
	 *
	 * Parses the form content, generates slugs for blocks that don't have one,
	 * ensures uniqueness, and updates the post content if needed.
	 *
	 * @param int      $post_id Post ID.
	 * @param \WP_Post $post    Post object.
	 * @return void
	 * @since 0.0.1
	 */
	public function update_field_slugs( $post_id, $post ) {
		// Skip autosave.
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		// Skip revisions.
		if ( wp_is_post_revision( $post_id ) ) {
			return;
		}

		// Verify user has permission to edit this post.
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		// Verify nonce - handles both block editor (REST API) and classic editor.
		if ( ! self::verify_save_post_nonce( $post_id ) ) {
			return;
		}

		$blocks = parse_blocks( $post->post_content );

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

		// Process blocks to generate slugs.
		[ $blocks, , $updated ] = \SureDonation\Inc\Helper::process_blocks( $blocks );

		// Only update if blocks were modified.
		if ( ! $updated ) {
			return;
		}

		// Serialize blocks and update post.
		$post_content = serialize_blocks( $blocks ); // @phpstan-ignore argument.type

		// Remove save action to prevent infinite loop.
		remove_action( 'save_post_' . self::POST_TYPE, [ $this, 'update_field_slugs' ], 10 );

		wp_update_post(
			[
				'ID'           => $post_id,
				'post_content' => $post_content,
			]
		);

		// Re-add save action.
		add_action( 'save_post_' . self::POST_TYPE, [ $this, 'update_field_slugs' ], 10, 2 );
	}

	/**
	 * Check if a post is a donation form.
	 *
	 * @param int|\WP_Post $post Post ID or post object.
	 * @return bool
	 * @since 0.0.1
	 */
	public static function is_donation_form( $post ) {
		$post = get_post( $post );

		if ( ! $post ) {
			return false;
		}

		return self::POST_TYPE === $post->post_type;
	}

	/**
	 * Get all donation forms.
	 *
	 * @param array<string, mixed> $args Additional WP_Query arguments.
	 * @return array<\WP_Post> Array of donation form posts.
	 * @since 0.0.1
	 */
	public static function get_forms( $args = [] ) {
		$defaults = [
			'post_type'      => self::POST_TYPE,
			'posts_per_page' => -1,
			'post_status'    => 'publish',
			'orderby'        => 'title',
			'order'          => 'ASC',
		];

		$query_args = wp_parse_args( $args, $defaults );

		return get_posts( $query_args ); // @phpstan-ignore return.type
	}

	/**
	 * Get forms linked to a specific campaign.
	 *
	 * @param int $campaign_id Campaign ID.
	 * @return array<\WP_Post> Array of donation form posts.
	 * @since 0.0.1
	 */
	public static function get_forms_by_campaign( $campaign_id ) {
		return self::get_forms(
			[
				'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
					[
						'key'     => self::META_CAMPAIGN_ID,
						'value'   => $campaign_id,
						'compare' => '=',
						'type'    => 'NUMERIC',
					],
				],
			]
		);
	}

	/**
	 * Get the campaign ID linked to a form.
	 *
	 * @param int $form_id Form ID.
	 * @return int Campaign ID or 0 if not linked.
	 * @since 0.0.1
	 */
	public static function get_form_campaign_id( $form_id ) {
		$campaign_id = get_post_meta( $form_id, self::META_CAMPAIGN_ID, true );
		return is_numeric( $campaign_id ) ? (int) $campaign_id : 0;
	}

	/**
	 * Set the campaign ID for a form.
	 *
	 * @param int $form_id     Form ID.
	 * @param int $campaign_id Campaign ID.
	 * @return bool True on success, false on failure.
	 * @since 0.0.1
	 */
	public static function set_form_campaign_id( $form_id, $campaign_id ) {
		return (bool) update_post_meta( $form_id, self::META_CAMPAIGN_ID, absint( $campaign_id ) );
	}

	/**
	 * Create a default donation form for a campaign.
	 *
	 * Creates a single-page form with all the essential fields matching
	 * the hardcoded template structure.
	 *
	 * @param int    $campaign_id   Campaign ID to link the form to.
	 * @param string $campaign_name Campaign name for the form title.
	 * @return int|false Form ID on success, false on failure.
	 * @since 0.0.1
	 */
	public static function create_default_form_for_campaign( $campaign_id, $campaign_name = '' ) {
		if ( ! $campaign_id ) {
			return false;
		}

		// Generate form title.
		$form_title = $campaign_name
			? sprintf(
				/* translators: %s: campaign name */
				__( '%s - Donation Form', 'suredonation' ),
				$campaign_name
			)
			: __( 'Donation Form', 'suredonation' );

		// Build the block content.
		$blocks_content = self::get_default_form_blocks_content();

		// Create the form post.
		$form_id = wp_insert_post(
			[
				'post_title'   => $form_title,
				'post_content' => $blocks_content,
				'post_status'  => 'publish',
				'post_type'    => self::POST_TYPE,
				'meta_input'   => [
					self::META_CAMPAIGN_ID => $campaign_id,
				],
			],
			true
		);

		if ( is_wp_error( $form_id ) ) {
			return false;
		}

		return $form_id;
	}

	/**
	 * Get the default block template for new forms.
	 *
	 * @return array<int, array<int, mixed>>
	 * @since 0.0.1
	 */
	private function get_default_template() {
		return [
			[
				'sd/multi-choice',
				[
					'label'      => __( 'Select Donation Amount', 'suredonation' ),
					'required'   => true,
					'choiceType' => 'radio',
					'layout'     => 'horizontal',
					'slug'       => 'donation-amount',
					'options'    => [
						[
							'label' => '$25',
							'value' => '25',
						],
						[
							'label' => '$50',
							'value' => '50',
						],
						[
							'label' => '$100',
							'value' => '100',
						],
						[
							'label' => '$250',
							'value' => '250',
						],
					],
				],
			],
			[
				'sd/input',
				[
					'label'       => __( 'Full Name', 'suredonation' ),
					'required'    => true,
					'placeholder' => __( 'Enter your full name', 'suredonation' ),
					'slug'        => 'donor-name',
					'fieldWidth'  => 50,
				],
			],
			[
				'sd/email',
				[
					'label'       => __( 'Email Address', 'suredonation' ),
					'required'    => true,
					'placeholder' => __( 'Enter your email', 'suredonation' ),
					'slug'        => 'donor-email',
					'fieldWidth'  => 50,
				],
			],
			[ 'sd/anonymous-donation', [ 'slug' => 'is-anonymous' ] ],
			[
				'sd/payment',
				[
					'gateway'             => 'stripe',
					'paymentType'         => 'one-time',
					'amountType'          => 'variable',
					'minimumAmount'       => 1,
					'variableAmountField' => 'donation-amount',
					'customerEmailField'  => 'donor-email',
					'customerNameField'   => 'donor-name',
				],
			],
			[
				'sd/donate-button',
				[
					'buttonText' => __( 'Complete Donation', 'suredonation' ),
					'slug'       => 'donate-button',
				],
			],
		];
	}

	/**
	 * Get the default form blocks content as serialized block markup.
	 *
	 * Creates a single-page donation form with:
	 * - Multi-choice for preset amounts (radio buttons)
	 * - Input for donor name
	 * - Email for donor email
	 * - Checkbox for covering fees
	 * - Checkbox for anonymous donation
	 * - Payment block configured for multi-choice variable amount
	 *
	 * @return string Serialized block content.
	 * @since 0.0.1
	 */
	private static function get_default_form_blocks_content() {
		$blocks = [];

		// Preset donation amounts using multi-choice (radio buttons).
		$blocks[] = '<!-- wp:sd/multi-choice ' . wp_json_encode(
			[
				'label'      => __( 'Select Donation Amount', 'suredonation' ),
				'required'   => true,
				'choiceType' => 'radio',
				'layout'     => 'horizontal',
				'slug'       => 'donation-amount',
				'options'    => [
					[
						'label' => '$25',
						'value' => '25',
					],
					[
						'label' => '$50',
						'value' => '50',
					],
					[
						'label' => '$100',
						'value' => '100',
					],
					[
						'label' => '$250',
						'value' => '250',
					],
				],
			]
		) . ' /-->';

		// Donor name.
		$blocks[] = '<!-- wp:sd/input ' . wp_json_encode(
			[
				'label'       => __( 'Full Name', 'suredonation' ),
				'required'    => true,
				'placeholder' => __( 'Enter your full name', 'suredonation' ),
				'slug'        => 'donor-name',
				'fieldWidth'  => 50,
			]
		) . ' /-->';

		// Donor email.
		$blocks[] = '<!-- wp:sd/email ' . wp_json_encode(
			[
				'label'       => __( 'Email Address', 'suredonation' ),
				'required'    => true,
				'placeholder' => __( 'Enter your email', 'suredonation' ),
				'slug'        => 'donor-email',
				'fieldWidth'  => 50,
			]
		) . ' /-->';

		// Anonymous donation block.
		$blocks[] = '<!-- wp:sd/anonymous-donation ' . wp_json_encode(
			[
				'slug' => 'is-anonymous',
			]
		) . ' /-->';

		// Payment block configured for multi-choice variable amount.
		$blocks[] = '<!-- wp:sd/payment ' . wp_json_encode(
			[
				'gateway'             => 'stripe',
				'paymentType'         => 'one-time',
				'amountType'          => 'variable',
				'minimumAmount'       => 1,
				'variableAmountField' => 'donation-amount',
				'customerEmailField'  => 'donor-email',
				'customerNameField'   => 'donor-name',
			]
		) . ' /-->';

		// Donate button.
		$blocks[] = '<!-- wp:sd/donate-button ' . wp_json_encode(
			[
				'buttonText' => __( 'Complete Donation', 'suredonation' ),
				'slug'       => 'donate-button',
			]
		) . ' /-->';

		return implode( "\n\n", $blocks );
	}

	/**
	 * Verify nonce for save_post hooks.
	 *
	 * Handles both block editor (REST API) and classic editor nonce verification.
	 * - Block Editor: Verifies the wp_rest nonce via REST_REQUEST constant
	 * - Classic Editor: Verifies _wpnonce with update-post_{$post_id} action
	 *
	 * @param int $post_id Post ID being saved.
	 * @return int|bool 1 if nonce is valid and generated between 0-12 hours (classic editor), 2 if valid and between 12-24 hours (classic editor), true for block editor, false otherwise.
	 * @since 0.0.1
	 */
	private static function verify_save_post_nonce( $post_id ) {
		// Block editor saves via REST API - nonce already verified by WordPress REST authentication.
		// The REST_REQUEST constant is only defined after successful authentication.
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return true;
		}

		// Classic editor - verify _wpnonce with update-post action.
		$nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';

		if ( ! is_string( $nonce ) || '' === $nonce ) {
			return false;
		}

		return wp_verify_nonce( $nonce, 'update-post_' . $post_id );
	}
}
