<?php
/**
 * Registration class file.
 *
 * Mirrors the pro's WOOWSN_Register class, stripped to free features.
 * Key behaviour matching:
 *  - ob_start on woocommerce_register_form_start → strip WC's hardcoded email field
 *  - Render ALL form fields (including first/last name) at priority 5
 *  - Save all fields including billing meta and WP user object fields
 *  - Approval workflow identical to pro
 *
 * @package WholesaleNode_For_WooCommerce
 */

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

// phpcs:disable WordPress.Security.NonceVerification.Missing -- WC registration hooks; nonce verified by WC core.
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment

class WOOWSNFree_Registration {

	private static $_instance = null;

	/** @var array Form field definitions. */
	private $form = array();

	/** @var bool Whether approval is required on registration. */
	private $require_approval = true;

	/** @var bool Whether to email admin on new registration. */
	private $notify_admin = true;

	public static function instance() {
		if ( is_null( self::$_instance ) ) {
			self::$_instance = new self();
		}
		return self::$_instance;
	}

	private function __construct() {
		$this->load_form();
		if ( ! empty( $this->form ) ) {
			$this->hooks();
		}
	}

	// ── Settings & form loading ───────────────────────────────────────

	private function load_form() {
		$saved = get_option( 'woowsn_free_registration_form', array() );
		if ( empty( $saved['fields'] ) ) {
			$saved = get_option( 'woowsn_registration_form', array() );
		}
		$this->form = isset( $saved['fields'] ) ? (array) $saved['fields'] : array();

		$all          = get_option( 'woowsn_free_settings_all', array() );
		$reg_settings = $all['registration'] ?? array();

		if ( empty( $reg_settings ) ) {
			$pro                    = get_option( 'woowsn_approval_settings', array() );
			$this->require_approval = isset( $pro['require_approval'] )  ? (bool) $pro['require_approval']  : true;
			$this->notify_admin     = isset( $pro['notify_on_register'] ) ? (bool) $pro['notify_on_register'] : true;
		} else {
			$this->require_approval = isset( $reg_settings['require_approval'] ) ? (bool) $reg_settings['require_approval'] : true;
			$this->notify_admin     = isset( $reg_settings['notify_admin'] )     ? (bool) $reg_settings['notify_admin']     : true;
		}
	}

	// ── Hooks — mirrors pro's setup_registration_form_modifications() ─

	private function hooks() {
		// Output-buffer WC's hardcoded email field and strip it — exactly like pro.
		add_action( 'woocommerce_register_form_start', array( $this, 'ob_start_strip_wc_email' ) );
		add_action( 'woocommerce_register_form',       array( $this, 'ob_end_strip_wc_email' ),   1 );

		// Render all custom fields at priority 5 — exactly like pro.
		add_action( 'woocommerce_register_form', array( $this, 'render_fields' ), 5 );

		// Validation & save.
		add_filter( 'woocommerce_registration_errors', array( $this, 'validate_fields' ), 10, 3 );
		add_action( 'woocommerce_created_customer',    array( $this, 'save_fields' ) );

		// Approval workflow.
		add_filter( 'woocommerce_registration_auth_new_customer', array( $this, 'maybe_block_auto_login' ), 10, 2 );
		add_action( 'woocommerce_account_content',                array( $this, 'maybe_show_pending_notice' ), 1 );
		add_filter( 'authenticate',                               array( $this, 'block_disabled_login' ), 30, 3 );

		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) );
	}

	// ── Email field stripping — identical to pro ──────────────────────

	/**
	 * Start output buffering before WC renders its email field.
	 */
	public function ob_start_strip_wc_email() {
		ob_start();
	}

	/**
	 * End buffer and strip WC's hardcoded reg_email input — identical to pro.
	 */
	public function ob_end_strip_wc_email() {
		$html = ob_get_clean();
		if ( $html === false ) {
			return;
		}
		// Same regex the pro uses.
		$html = preg_replace(
			'/<p[^>]*>\s*<label[^>]*for=["\']reg_email["\'][^>]*>.*?<\/label>\s*<input[^>]*id=["\']reg_email["\'][^>]*\/?>[^<]*<\/p>/s',
			'',
			$html
		);
		echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	// ── Field rendering — mirrors pro's add_custom_registration_fields() ─

	/**
	 * Render all form fields — mirrors pro's add_custom_registration_fields().
	 *
	 * WC mode detection:
	 *  generate_username=yes → WC auto-generates, ignore account_username field entirely.
	 *  generate_password=yes → WC emails a password link, ignore account_password field.
	 *  Both =no             → WC renders them natively, skip ours to avoid duplicates.
	 */
	public function render_fields() {
		if ( empty( $this->form ) ) {
			return;
		}

		$wc_generates_username = get_option( 'woocommerce_registration_generate_username' ) === 'yes';
		$wc_generates_password = get_option( 'woocommerce_registration_generate_password' ) === 'yes';

		// Fields to skip entirely:
		// - generate=yes  → WC handles it silently (auto), we show nothing.
		// - generate=no   → WC renders its own field, skip ours to avoid duplicates.
		// Either way we always skip account_username and account_password from our loop.
		$always_skip = array( 'account_username', 'account_password' );

		$fields = $this->form;
		usort( $fields, function( $a, $b ) {
			return ( $a['order'] ?? 0 ) - ( $b['order'] ?? 0 );
		} );

		// Show info notice when WC is in passwordless mode.
		if ( $wc_generates_password ) {
			echo '<p class="woocommerce-privacy-policy-text woowsn-password-notice" style="background:#f0f9ff;border:1px solid #bae6fd;border-radius:4px;padding:10px 14px;margin:0 0 12px;font-size:13px;color:#0369a1;">';
			echo esc_html__( 'A link to set your password will be emailed to you after registration.', 'wholesalenode-for-woocommerce' );
			echo '</p>';
		}

		// Show info notice when WC auto-generates the username.
		if ( $wc_generates_username ) {
			echo '<p class="woocommerce-privacy-policy-text woowsn-username-notice" style="background:#f0f9ff;border:1px solid #bae6fd;border-radius:4px;padding:10px 14px;margin:0 0 12px;font-size:13px;color:#0369a1;">';
			echo esc_html__( 'Your username will be generated automatically from your email address.', 'wholesalenode-for-woocommerce' );
			echo '</p>';
		}

		echo '<div class="woocommerce-billing-fields woowsn-free-reg-fields">';

		foreach ( $fields as $field ) {
			$type = $field['type'] ?? 'text';
			$name = $field['name'] ?? '';

			// Always skip username/password — WC handles both cases (native or auto-generate).
			if ( in_array( $name, $always_skip, true ) ) {
				continue;
			}
			if ( in_array( $type, array( 'column', 'group', 'group_start', 'group_end', 'user_group_select' ), true ) ) {
				continue;
			}

			$this->render_field( $field );
		}

		echo '</div>';
	}

	/**
	 * Render a single field using WC-compatible markup.
	 * Mirrors pro's render_registration_field() for the field types free supports.
	 *
	 * @param array $field Field definition.
	 */
	private function render_field( $field ) {
		$type     = $field['type']        ?? 'text';
		$name     = $field['name']        ?? '';
		$label    = $field['label']       ?? '';
		$ph       = $field['placeholder'] ?? '';
		$required = ! empty( $field['required'] );

		if ( ! $name ) {
			return;
		}

		// billing_email is submitted as name="email" by WC — use that submit name.
		$submit_name = ( $name === 'billing_email' ) ? 'email' : $name;

		$value = '';
		if ( isset( $_POST[ $submit_name ] ) ) {
			$value = sanitize_text_field( wp_unslash( $_POST[ $submit_name ] ) );
		} elseif ( isset( $_POST[ $name ] ) ) {
			$value = sanitize_text_field( wp_unslash( $_POST[ $name ] ) );
		}

		$req_attr = $required ? ' required' : '';
		$req_mark = $required ? '<span class="required">*</span>' : '';

		$classes = 'woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide';
		if ( $required ) {
			$classes .= ' validate-required';
		}
		if ( $type === 'email' || $name === 'billing_email' ) {
			$classes .= ' validate-email';
		}
		?>
		<p class="<?php echo esc_attr( $classes ); ?>">
			<label for="<?php echo esc_attr( $name ); ?>">
				<?php echo esc_html( $label ); ?> <?php echo $req_mark; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</label>
			<?php if ( $type === 'textarea' ) : ?>
				<textarea id="<?php echo esc_attr( $name ); ?>"
					name="<?php echo esc_attr( $name ); ?>"
					class="woocommerce-Input input-text"
					placeholder="<?php echo esc_attr( $ph ); ?>"
					<?php echo $req_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				><?php echo esc_textarea( $value ); ?></textarea>

			<?php elseif ( $type === 'checkbox' ) : ?>
				<input type="checkbox"
					id="<?php echo esc_attr( $name ); ?>"
					name="<?php echo esc_attr( $name ); ?>"
					class="woocommerce-Input"
					value="1"
					<?php checked( $value, '1' ); ?>
					<?php echo $req_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				/>

			<?php elseif ( $type === 'select' ) : ?>
				<select id="<?php echo esc_attr( $name ); ?>"
					name="<?php echo esc_attr( $name ); ?>"
					class="woocommerce-Input"
					<?php echo $req_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				>
					<option value=""><?php esc_html_e( '— Select —', 'wholesalenode-for-woocommerce' ); ?></option>
					<?php foreach ( (array) ( $field['options'] ?? array() ) as $opt ) : ?>
						<option value="<?php echo esc_attr( $opt ); ?>" <?php selected( $value, $opt ); ?>>
							<?php echo esc_html( $opt ); ?>
						</option>
					<?php endforeach; ?>
				</select>

			<?php else : ?>
				<?php
				// password, text, email, tel, number, date — all use <input>.
				// Use submit_name so billing_email posts as "email".
				?>
				<input
					type="<?php echo esc_attr( $type ); ?>"
					id="<?php echo esc_attr( $name ); ?>"
					name="<?php echo esc_attr( $submit_name ); ?>"
					class="woocommerce-Input woocommerce-Input--text input-text"
					value="<?php echo ( $type === 'password' ) ? '' : esc_attr( $value ); ?>"
					placeholder="<?php echo esc_attr( $ph ); ?>"
					<?php echo $req_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				/>
			<?php endif; ?>
		</p>
		<?php
	}

	// ── Validation ────────────────────────────────────────────────────

	/**
	 * Validate required fields — mirrors pro's validate_custom_fields().
	 *
	 * @param WP_Error $errors   WC errors object.
	 * @param string   $username Submitted username.
	 * @param string   $email    Submitted email.
	 * @return WP_Error
	 */
	public function validate_fields( $errors, $username, $email ) {
		// WC-native fields already validated by WC core.
		$wc_core = array( 'billing_email', 'account_username', 'account_password' );

		foreach ( $this->form as $field ) {
			$type = $field['type'] ?? 'text';
			$name = $field['name'] ?? '';

			if ( in_array( $name, $wc_core, true ) ) {
				continue;
			}
			if ( in_array( $type, array( 'column', 'group', 'group_start', 'group_end', 'user_group_select' ), true ) ) {
				continue;
			}
			if ( empty( $field['required'] ) ) {
				continue;
			}

			$submit_name = ( $name === 'billing_email' ) ? 'email' : $name;
			$value       = isset( $_POST[ $submit_name ] ) ? sanitize_text_field( wp_unslash( $_POST[ $submit_name ] ) ) : '';
			if ( empty( $value ) ) {
				$errors->add(
					'woowsn_' . $name,
					sprintf(
						/* translators: %s: field label */
						'<strong>' . __( 'Error:', 'wholesalenode-for-woocommerce' ) . '</strong> ' . __( '%s is required.', 'wholesalenode-for-woocommerce' ),
						esc_html( $field['label'] ?? $name )
					)
				);
			}
		}
		return $errors;
	}

	// ── Save ──────────────────────────────────────────────────────────

	/**
	 * Save fields after WC creates the customer — mirrors pro's save_custom_fields().
	 *
	 * Also saves billing meta to the WP user object for fields like
	 * billing_first_name / billing_last_name so WC shows them in the
	 * customer profile.
	 *
	 * @param int $user_id New customer ID.
	 */
	public function save_fields( $user_id ) {
		// WC saves these itself from $_POST — don't duplicate.
		$wc_core     = array( 'billing_email', 'account_username', 'account_password' );
		$group_assigned = false;
		$approval_set   = false;

		// WP user object fields that map from billing meta.
		$user_obj_map = array(
			'billing_first_name' => 'first_name',
			'billing_last_name'  => 'last_name',
		);

		foreach ( $this->form as $field ) {
			$type = $field['type'] ?? 'text';
			$name = $field['name'] ?? '';

			if ( in_array( $name, $wc_core, true ) ) {
				continue;
			}

			if ( $type === 'user_group_select' ) {
				$group_id = $this->resolve_group_from_post( $field );
				if ( ! $group_id ) {
					$group_id = $this->get_b2b_default_group_id();
				}
				if ( $group_id ) {
					update_user_meta( $user_id, 'woowsn_user_group', $group_id );
					$group_assigned = true;
					$this->apply_group_role_and_approval( $user_id, $group_id, $approval_set );
					$approval_set = true;
				}
				continue;
			}

			if ( in_array( $type, array( 'column', 'group', 'group_start', 'group_end' ), true ) ) {
				continue;
			}

			$submit_name = ( $name === 'billing_email' ) ? 'email' : $name;
			if ( ! isset( $_POST[ $submit_name ] ) && ! isset( $_POST[ $name ] ) ) {
				continue;
			}

			$raw = isset( $_POST[ $submit_name ] ) ? wp_unslash( $_POST[ $submit_name ] ) : wp_unslash( $_POST[ $name ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$value = ( $type === 'textarea' )
				? sanitize_textarea_field( wp_unslash( $raw ) )
				: sanitize_text_field( wp_unslash( $raw ) );

			// Save as user meta (billing_ prefix fields are stored as usermeta by WC).
			update_user_meta( $user_id, $name, $value );

			// Also update the WP user object for first/last name so it shows correctly.
			if ( isset( $user_obj_map[ $name ] ) ) {
				wp_update_user( array(
					'ID'                        => $user_id,
					$user_obj_map[ $name ]      => $value,
				) );
			}
		}

		// Mark as B2B user.
		update_user_meta( $user_id, 'woowsn_user_type', 'b2b' );

		// Fallback group assignment if no group_select field in form.
		if ( ! $group_assigned ) {
			$group_id = $this->get_b2b_default_group_id();
			if ( $group_id ) {
				update_user_meta( $user_id, 'woowsn_user_group', $group_id );
			}
		}

		// Approval status.
		if ( ! $approval_set ) {
			if ( $this->require_approval ) {
				update_user_meta( $user_id, 'woowsn_approval_status', 'pending' );
				update_user_meta( $user_id, 'woowsn_account_disabled', '1' );
			} else {
				update_user_meta( $user_id, 'woowsn_approval_status', 'approved' );
			}
		}

		if ( $this->notify_admin ) {
			$this->send_admin_notification( $user_id );
		}
	}

	// ── Group helpers ─────────────────────────────────────────────────

	/**
	 * Resolve submitted group label → DB group ID.
	 *
	 * @param array $field Field definition.
	 * @return int
	 */
	private function resolve_group_from_post( $field ) {
		$field_name     = $field['name'] ?? 'woowsn_user_group';
		$selected_label = isset( $_POST[ $field_name ] ) ? sanitize_text_field( wp_unslash( $_POST[ $field_name ] ) ) : '';

		if ( ! $selected_label ) {
			$ids = $field['groupIds'] ?? array();
			return ! empty( $ids ) ? (int) reset( $ids ) : 0;
		}

		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_user_groups';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
			return 0;
		}

		$child = strpos( $selected_label, ' › ' ) !== false
			? trim( explode( ' › ', $selected_label, 2 )[1] )
			: $selected_label;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return (int) $wpdb->get_var(
			$wpdb->prepare( 'SELECT id FROM %i WHERE title = %s AND status = %s AND parent_id > 0 LIMIT 1', $table, $child, 'active' )
		);
	}

	/**
	 * Apply group's auto_assign_roles and set approval status from group permissions.
	 *
	 * @param int  $user_id      User ID.
	 * @param int  $group_id     Group ID.
	 * @param bool $approval_set Whether approval was already set.
	 */
	private function apply_group_role_and_approval( $user_id, $group_id, $approval_set ) {
		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_user_groups';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$group = $wpdb->get_row(
			$wpdb->prepare( 'SELECT auto_assign_roles, permissions FROM %i WHERE id = %d LIMIT 1', $table, $group_id ),
			ARRAY_A
		);

		$user = new WP_User( $user_id );
		if ( $group ) {
			$roles = json_decode( $group['auto_assign_roles'] ?? '[]', true );
			$user->set_role( ( ! empty( $roles ) && is_array( $roles ) ) ? sanitize_key( $roles[0] ) : 'wholesaler' );
		} else {
			$user->set_role( 'wholesaler' );
		}

		if ( ! $approval_set && $group ) {
			$perms    = json_decode( $group['permissions'] ?? '{}', true );
			$req_appr = ! empty( $perms['require_approval'] ) || $this->require_approval;
			if ( $req_appr ) {
				update_user_meta( $user_id, 'woowsn_approval_status', 'pending' );
				update_user_meta( $user_id, 'woowsn_account_disabled', '1' );
			} else {
				update_user_meta( $user_id, 'woowsn_approval_status', 'approved' );
			}
		}
	}

	/**
	 * Get the b2b_default group ID.
	 *
	 * @return int
	 */
	private function get_b2b_default_group_id() {
		global $wpdb;
		$table     = $wpdb->prefix . 'woowsn_user_groups';
		$cache_key = 'woowsn_free_b2b_default_group_id';
		$cached    = wp_cache_get( $cache_key, 'woowsn_free' );
		if ( false !== $cached ) {
			return (int) $cached;
		}
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
			return 0;
		}
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$id = $wpdb->get_var( $wpdb->prepare( 'SELECT id FROM %i WHERE slug = %s LIMIT 1', $table, 'b2b_default' ) );
		wp_cache_set( $cache_key, (int) $id, 'woowsn_free', HOUR_IN_SECONDS );
		return (int) $id;
	}

	// ── Approval workflow ─────────────────────────────────────────────

	/**
	 * Prevent WC from auto-logging in a pending/rejected user after registration.
	 *
	 * @param bool $auth    Whether WC should auto-login.
	 * @param int  $user_id New user ID.
	 * @return bool
	 */
	public function maybe_block_auto_login( $auth, $user_id ) {
		$status = get_user_meta( $user_id, 'woowsn_approval_status', true );
		if ( $status === 'pending' ) {
			wc_add_notice( __( 'Thank you for registering. Your account is awaiting admin approval. You will receive an email once it is reviewed.', 'wholesalenode-for-woocommerce' ), 'success' );
			return false;
		}
		if ( $status === 'rejected' ) {
			wc_add_notice( __( 'Your account registration was not approved. Please contact us for assistance.', 'wholesalenode-for-woocommerce' ), 'error' );
			return false;
		}
		return $auth;
	}

	/**
	 * Show pending notice on My Account page.
	 */
	public function maybe_show_pending_notice() {
		if ( ! is_user_logged_in() ) {
			return;
		}
		$status = get_user_meta( get_current_user_id(), 'woowsn_approval_status', true );
		if ( $status !== 'pending' ) {
			return;
		}
		echo '<div class="woocommerce-message" style="margin-top:2rem;">'
			. '<strong>' . esc_html__( 'Account Pending Approval', 'wholesalenode-for-woocommerce' ) . '</strong><br>'
			. esc_html__( 'Your account is awaiting admin approval. You will receive an email once it is reviewed.', 'wholesalenode-for-woocommerce' )
			. '</div>';
	}

	/**
	 * Block login for disabled accounts.
	 *
	 * @param WP_User|WP_Error|null $user     Authenticated user.
	 * @param string                $username Username.
	 * @param string                $password Password.
	 * @return WP_User|WP_Error
	 */
	public function block_disabled_login( $user, $username, $password ) {
		if ( ! $user instanceof WP_User ) {
			return $user;
		}
		if ( get_user_meta( $user->ID, 'woowsn_account_disabled', true ) !== '1' ) {
			return $user;
		}
		$status = get_user_meta( $user->ID, 'woowsn_approval_status', true );
		if ( $status === 'pending' ) {
			return new WP_Error( 'woowsn_pending', __( '<strong>Error:</strong> Your account is pending admin approval. You will receive an email once approved.', 'wholesalenode-for-woocommerce' ) );
		}
		return new WP_Error( 'woowsn_rejected', __( '<strong>Error:</strong> Your account has not been approved. Please contact us for assistance.', 'wholesalenode-for-woocommerce' ) );
	}

	// ── Admin notification ────────────────────────────────────────────

	/**
	 * Email admin about a new pending B2B registration.
	 *
	 * @param int $user_id New user ID.
	 */
	private function send_admin_notification( $user_id ) {
		$user    = get_userdata( $user_id );
		$site    = get_bloginfo( 'name' );
		$to      = get_option( 'admin_email' );
		/* translators: %s: site name */
		$subject = sprintf( __( '[%s] New B2B Registration Pending Approval', 'wholesalenode-for-woocommerce' ), $site );
		$body    = sprintf(
			/* translators: 1: name, 2: email, 3: site, 4: review URL */
			__( "A new B2B user has registered and is waiting for your approval.\n\nName: %1\$s\nEmail: %2\$s\nSite: %3\$s\n\nReview:\n%4\$s", 'wholesalenode-for-woocommerce' ),
			$user ? $user->display_name : '',
			$user ? $user->user_email   : '',
			$site,
			admin_url( 'users.php?woowsn_approval_filter=pending' )
		);
		wp_mail( $to, $subject, $body );
	}

	// ── Assets ────────────────────────────────────────────────────────

	/**
	 * Enqueue frontend CSS on account pages.
	 */
	public function enqueue_styles() {
		if ( is_account_page() ) {
			wp_enqueue_style( 'woowsn-free-frontend', WOOWSN_FREE_URL . 'assets/css/frontend.css', array(), WOOWSN_FREE_VERSION );
		}
	}
}
