<?php
/**
 * Admin Setup Wizard (guided initial configuration).
 *
 * Writes to the same option keys as the existing Settings UI so the wizard is a UX layer,
 * not a parallel configuration system.
 *
 * @package WLC_Algolia
 */

namespace wlc_algolia;

use Algolia\AlgoliaSearch\Api\SearchClient;
use function wlc_algolia\Helpers\wlc_retrieve_searchable_post_type_names;

defined( 'ABSPATH' ) || die( 'Direct file access is forbidden' );

/**
 * Admin Setup Wizard class.
 */
class Admin_Wizard {
	/**
	 * Init hooks.
	 *
	 * @return void
	 */
	public function hooks(): void {
		add_action( 'admin_post_wlc_algolia_wizard_save', array( $this, 'handle_save' ) );
		add_action( 'admin_post_wlc_algolia_wizard_test_connection', array( $this, 'handle_test_connection' ) );
		add_action( 'wp_ajax_wlc_algolia_wizard_test_connection', array( $this, 'ajax_test_connection' ) );
	}

	/**
	 * Current wizard step.
	 *
	 * @return int
	 */
	public static function get_step(): int {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$step = isset( $_GET['step'] ) ? absint( $_GET['step'] ) : 1;
		return max( 1, min( 5, $step ) );
	}

	/**
	 * Wizard state (per-user).
	 *
	 * @return array
	 */
	public static function get_state(): array {
		$user_id = get_current_user_id();
		if ( ! $user_id ) {
			return array();
		}
		$state = get_user_meta( $user_id, 'wlc_algolia_wizard_state', true );
		return is_array( $state ) ? $state : array();
	}

	/**
	 * Get available schema fields for a post type (used by wizard Step 3).
	 *
	 * Mirrors Algolia_Index_Helper::wlc_prepare_cpt_fields_array() but does not rely on $_GET.
	 *
	 * @param string $post_type Post type.
	 * @return array<int, string>
	 */
	public static function get_available_fields_for_post_type( string $post_type ): array {
		$post_type = sanitize_key( $post_type );
		if ( '' === $post_type || ! post_type_exists( $post_type ) ) {
			return array();
		}

		$args = array(
			'post_type'      => $post_type,
			'posts_per_page' => 1,
			'post_status'    => 'any',
			'fields'         => 'all',
		);

		$posts      = get_posts( $args );
		$cpt_fields = array();
		if ( ! empty( $posts ) ) {
			foreach ( $posts as $p ) {
				if ( $p instanceof \WP_Post ) {
					unset( $p->post_password );
					$cpt_fields = array_keys( (array) $p );
					break;
				}
			}
		}

		// WooCommerce important meta keys.
		if ( 'product' === $post_type ) {
			$wc_meta_fields = array(
				'_sku',
				'_price',
				'_regular_price',
				'_sale_price',
				'_stock',
				'_stock_status',
				'_manage_stock',
				'_weight',
				'_length',
				'_width',
				'_height',
			);
			$cpt_fields     = array_values( array_unique( array_merge( $cpt_fields, $wc_meta_fields ) ) );
		}

		$cpt_fields[] = 'post_permalink';
		$cpt_fields[] = 'taxonomies';
		$cpt_fields[] = 'images';

		$cpt_fields = array_values( array_unique( array_filter( $cpt_fields ) ) );

		return $cpt_fields;
	}

	/**
	 * Update wizard state (per-user).
	 *
	 * @param array $state State.
	 * @return void
	 */
	private static function set_state( array $state ): void {
		$user_id = get_current_user_id();
		if ( ! $user_id ) {
			return;
		}
		update_user_meta( $user_id, 'wlc_algolia_wizard_state', $state );
	}

	/**
	 * Redirect back to wizard with status message.
	 *
	 * @param int    $step Step.
	 * @param string $status success|error.
	 * @param string $message Message.
	 * @return void
	 */
	private function redirect( int $step, string $status = '', string $message = '' ): void {
		$url = admin_url( 'admin.php?page=wlc-algolia&tab=wizard&step=' . absint( $step ) );
		if ( '' !== $status ) {
			$url = add_query_arg( 'wlc_status', sanitize_key( $status ), $url );
		}
		if ( '' !== $message ) {
			$url = add_query_arg( 'wlc_message', rawurlencode( $message ), $url );
		}
		wp_safe_redirect( $url );
		exit;
	}

	/**
	 * Basic sanitization compatible with Settings::sanitize_settings_array().
	 *
	 * @param mixed $input Input.
	 * @return mixed
	 */
	private function sanitize_deep( $input ) {
		if ( is_array( $input ) ) {
			$out = array();
			foreach ( $input as $k => $v ) {
				$key = (string) $k;
				if ( is_array( $v ) ) {
					$out[ $k ] = $this->sanitize_deep( $v );
					continue;
				}

				if ( 'max-suggestions' === $key ) {
					$out[ $k ] = (string) absint( $v );
					continue;
				}

				if (
					'enable' === $key
					|| 'index' === $key
					|| 'facet' === $key
					|| 'enable-algolia' === $key
					|| 'enable-autocomplete' === $key
					|| str_contains( $key, 'enable' )
					|| str_contains( $key, 'autosuggestion' )
				) {
					$out[ $k ] = (string) ( (int) ( '1' === (string) $v || 1 === $v || true === $v ) );
					continue;
				}

				if ( 'type' === $key ) {
					$out[ $k ] = sanitize_key( (string) $v );
					continue;
				}

				if ( 'selected_post_type' === $key ) {
					$out[ $k ] = sanitize_key( (string) $v );
					continue;
				}

				$out[ $k ] = sanitize_text_field( (string) $v );
			}
			return $out;
		}

		return sanitize_text_field( (string) $input );
	}

	/**
	 * Handle "Test connection" action.
	 *
	 * @return void
	 */
	public function handle_test_connection(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You are not allowed to perform this action.', 'wlc-search-integration-with-algolia' ) );
		}

		check_admin_referer( 'wlc_algolia_wizard_test_connection', 'wlc_algolia_wizard_nonce' );

		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$app_id = isset( $_POST['application_id'] ) ? sanitize_text_field( wp_unslash( $_POST['application_id'] ) ) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$write_key = isset( $_POST['write_api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['write_api_key'] ) ) : '';

		if ( '' === $app_id || '' === $write_key ) {
			$this->redirect( 1, 'error', __( 'Missing Application ID or Write API Key.', 'wlc-search-integration-with-algolia' ) );
		}

		try {
			$client = SearchClient::create( $app_id, $write_key );
			// Best-effort ping: list indices (if available) or try a lightweight call.
			$ok = true;
			if ( method_exists( $client, 'listIndices' ) ) {
				$client->listIndices();
			} elseif ( method_exists( $client, 'getLogs' ) ) {
				$client->getLogs( 1 );
			}
			if ( $ok ) {
				$this->redirect( 1, 'success', __( 'Connection OK. Keys look valid.', 'wlc-search-integration-with-algolia' ) );
			}
		} catch ( \Throwable $e ) {
			$this->redirect(
				1,
				'error',
				sprintf(
					/* translators: %s: error message */
					__( 'Connection failed: %s', 'wlc-search-integration-with-algolia' ),
					$e->getMessage()
				)
			);
		}
	}

	/**
	 * AJAX: Test Algolia connection (no reload).
	 *
	 * @return void
	 */
	public function ajax_test_connection(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'You are not allowed to perform this action.', 'wlc-search-integration-with-algolia' ),
				),
				403
			);
		}

		if ( ! check_ajax_referer( 'wlc_algolia_wizard_test_connection', 'nonce', false ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Invalid nonce.', 'wlc-search-integration-with-algolia' ),
				),
				400
			);
		}

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$app_id = isset( $_POST['application_id'] ) ? sanitize_text_field( wp_unslash( $_POST['application_id'] ) ) : '';
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$write_key = isset( $_POST['write_api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['write_api_key'] ) ) : '';

		if ( '' === $app_id || '' === $write_key ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Please provide Application ID and Write API Key.', 'wlc-search-integration-with-algolia' ),
				),
				400
			);
		}

		try {
			$client        = SearchClient::create( $app_id, $write_key );
			$indices_count = null;
			if ( method_exists( $client, 'listIndices' ) ) {
				// The v4 client returns responses as associative arrays.
				$list = $client->listIndices();
				if ( is_array( $list ) && isset( $list['items'] ) && is_array( $list['items'] ) ) {
					$indices_count = count( $list['items'] );
				}
			} elseif ( method_exists( $client, 'getLogs' ) ) {
				$client->getLogs( 1 );
			}

			wp_send_json_success(
				array(
					'message'      => esc_html__( 'Connection OK. Keys look valid.', 'wlc-search-integration-with-algolia' ),
					'indicesCount' => $indices_count,
				)
			);
		} catch ( \Throwable $e ) {
			wp_send_json_error(
				array(
					'message' => sprintf(
						/* translators: %s: error message */
						esc_html__( 'Connection failed: %s', 'wlc-search-integration-with-algolia' ),
						$e->getMessage()
					),
				),
				400
			);
		}
	}

	/**
	 * Handle saving wizard step.
	 *
	 * @return void
	 */
	public function handle_save(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You are not allowed to perform this action.', 'wlc-search-integration-with-algolia' ) );
		}

		check_admin_referer( 'wlc_algolia_wizard_save', 'wlc_algolia_wizard_nonce' );

		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$step = isset( $_POST['step'] ) ? absint( $_POST['step'] ) : 1;
		$step = max( 1, min( 5, $step ) );

		$state              = self::get_state();
		$state['last_step'] = $step;

		switch ( $step ) {
			case 1:
				$this->save_step_1_general();
				$state['completed_steps'][1] = true;
				self::set_state( $state );
				$this->redirect( 2, 'success', __( 'API settings saved.', 'wlc-search-integration-with-algolia' ) );
				break;
			case 2:
				$this->save_step_2_post_types();
				$state['completed_steps'][2] = true;
				self::set_state( $state );
				$this->redirect( 3, 'success', __( 'Post types saved.', 'wlc-search-integration-with-algolia' ) );
				break;
			case 3:
				$this->save_step_3_schema();
				$state['completed_steps'][3] = true;
				self::set_state( $state );
				$this->redirect( 4, 'success', __( 'Schema & facets saved.', 'wlc-search-integration-with-algolia' ) );
				break;
			case 4:
				$this->save_step_4_autocomplete();
				$state['completed_steps'][4] = true;
				self::set_state( $state );
				$this->redirect( 5, 'success', __( 'Autocomplete saved.', 'wlc-search-integration-with-algolia' ) );
				break;
			case 5:
			default:
				$state['completed_steps'][5] = true;
				$state['completed_at']       = time();
				self::set_state( $state );
				update_user_meta( get_current_user_id(), 'wlc_algolia_wizard_completed', 1 );
				$this->redirect( 5, 'success', __( 'Wizard completed. You can now use Advanced settings tabs.', 'wlc-search-integration-with-algolia' ) );
		}
	}

	/**
	 * Step 1: Save general/API settings into wlc_algolia_settings_general (nested under ['general']).
	 *
	 * @return void
	 */
	private function save_step_1_general(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$raw     = isset( $_POST['general'] ) && is_array( $_POST['general'] ) ? wp_unslash( $_POST['general'] ) : array();
		$general = is_array( $raw ) ? $this->sanitize_deep( $raw ) : array();

		$existing = get_option( 'wlc_algolia_settings_general', array() );
		$existing = is_array( $existing ) ? $existing : array();

		$existing['general'] = array_merge(
			$existing['general'] ?? array(),
			array(
				'enable_algolia' => $general['enable_algolia'] ?? '0',
				'application_id' => $general['application_id'] ?? '',
				'search_api_key' => $general['search_api_key'] ?? '',
				'write_api_key'  => $general['write_api_key'] ?? '',
				'index_name'     => $general['index_name'] ?? '',
			)
		);

		update_option( 'wlc_algolia_settings_general', $existing, false );

		// Store preset for UX defaults in later steps (per-user state).
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$preset = isset( $_POST['preset'] ) ? sanitize_key( wp_unslash( $_POST['preset'] ) ) : '';
		if ( in_array( $preset, array( 'woo', 'content', 'mixed' ), true ) ) {
			$state           = self::get_state();
			$state['preset'] = $preset;
			self::set_state( $state );
		}
	}

	/**
	 * Step 2: Save post types into wlc_algolia_settings_post_type_index (flat array).
	 *
	 * @return void
	 */
	private function save_step_2_post_types(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$raw_selected = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? wp_unslash( $_POST['post_types'] ) : array();
		$selected     = array_values(
			array_filter(
				array_map( 'sanitize_key', is_array( $raw_selected ) ? $raw_selected : array() )
			)
		);

		$post_types = wlc_retrieve_searchable_post_type_names();
		$post_types = is_array( $post_types ) ? $post_types : array();

		$option = get_option( 'wlc_algolia_settings_post_type_index', array() );
		$option = is_array( $option ) ? $option : array();

		// Normalize: set 0 for all public post types, then set selected to 1.
		foreach ( $post_types as $pt ) {
			$pt            = sanitize_key( (string) $pt );
			$option[ $pt ] = in_array( $pt, $selected, true ) ? '1' : '0';
			// Keep wizard default: autosuggestion off; can be enabled later in Post Type tab.
			$option[ $pt . '_autosuggestion' ] = $option[ $pt . '_autosuggestion' ] ?? '0';
			$option[ $pt . '_custom_index' ]   = $option[ $pt . '_custom_index' ] ?? '';
		}

		update_option( 'wlc_algolia_settings_post_type_index', $this->sanitize_deep( $option ), false );

		// Persist "primary post type" for next step defaults (if not set).
		$state = self::get_state();
		if ( empty( $state['primary_post_type'] ) ) {
			$state['primary_post_type'] = $selected[0] ?? ( $post_types[0] ?? 'post' );
		}
		$state['selected_post_types'] = $selected;
		self::set_state( $state );
	}

	/**
	 * Step 3: Save CPT mapping for one post type into wlc_algolia_settings_cpt_{post_type}.
	 *
	 * @return void
	 */
	private function save_step_3_schema(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$post_type = isset( $_POST['primary_post_type'] ) ? sanitize_key( wp_unslash( $_POST['primary_post_type'] ) ) : '';
		if ( '' === $post_type ) {
			$state     = self::get_state();
			$post_type = isset( $state['primary_post_type'] ) ? sanitize_key( (string) $state['primary_post_type'] ) : 'post';
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$raw_fields   = isset( $_POST['index_fields'] ) && is_array( $_POST['index_fields'] ) ? wp_unslash( $_POST['index_fields'] ) : array();
		$index_fields = array_values( array_filter( array_map( 'sanitize_key', $raw_fields ) ) );

		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$raw_facets   = isset( $_POST['facet_fields'] ) && is_array( $_POST['facet_fields'] ) ? wp_unslash( $_POST['facet_fields'] ) : array();
		$facet_fields = array_values( array_filter( array_map( 'sanitize_key', $raw_facets ) ) );

		// Recommended fields baseline.
		$recommended = array( 'ID', 'post_title', 'post_content', 'post_excerpt', 'post_date', 'post_permalink', 'images', 'taxonomies' );
		if ( 'product' === $post_type ) {
			$recommended = array_merge( $recommended, array( '_price', '_regular_price', '_sale_price', '_sku' ) );
		}

		$all_fields = array_values( array_unique( array_merge( $recommended, $index_fields, $facet_fields ) ) );

		$defaults = array();
		if ( class_exists( '\wlc_algolia\Cpt_Defaults' ) ) {
			$defaults = \wlc_algolia\Cpt_Defaults::get_default_index_fields();
		}

		$option_value = array(
			'selected_post_type' => $post_type,
		);

		foreach ( $all_fields as $field ) {
			$field = sanitize_key( (string) $field );
			if ( '' === $field ) {
				continue;
			}
			$default_type           = $defaults[ $post_type ][ $field ]['type'] ?? 'string';
			$option_value[ $field ] = array(
				'type'  => sanitize_key( (string) $default_type ),
				'index' => in_array( $field, $index_fields, true ) ? '1' : '0',
				'facet' => in_array( $field, $facet_fields, true ) ? '1' : '0',
			);
		}

		update_option( 'wlc_algolia_settings_cpt_' . $post_type, $this->sanitize_deep( $option_value ), false );

		$state                      = self::get_state();
		$state['primary_post_type'] = $post_type;
		self::set_state( $state );
	}

	/**
	 * Step 4: Save autocomplete into wlc_algolia_settings_autocomplete (flat array).
	 *
	 * @return void
	 */
	private function save_step_4_autocomplete(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$enabled = isset( $_POST['enable_autocomplete'] ) ? ( '1' === (string) wp_unslash( $_POST['enable_autocomplete'] ) ? '1' : '0' ) : '0';

		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$raw_selected = isset( $_POST['autocomplete_post_types'] ) && is_array( $_POST['autocomplete_post_types'] ) ? wp_unslash( $_POST['autocomplete_post_types'] ) : array();
		$selected     = array_values( array_filter( array_map( 'sanitize_key', $raw_selected ) ) );

		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$max = isset( $_POST['max_suggestions'] ) ? absint( wp_unslash( $_POST['max_suggestions'] ) ) : 10;

		$option                        = get_option( 'wlc_algolia_settings_autocomplete', array() );
		$option                        = is_array( $option ) ? $option : array();
		$option['enable-autocomplete'] = $enabled;

		$post_types = wlc_retrieve_searchable_post_type_names();
		$post_types = is_array( $post_types ) ? $post_types : array();

		$general    = get_option( 'wlc_algolia_settings_general', array() );
		$base_index = is_array( $general ) && isset( $general['general']['index_name'] ) ? (string) $general['general']['index_name'] : '';

		foreach ( $post_types as $pt ) {
			$pt = sanitize_key( (string) $pt );
			if ( '' === $pt ) {
				continue;
			}
			$is_on         = in_array( $pt, $selected, true ) ? '1' : '0';
			$option[ $pt ] = array_merge(
				$option[ $pt ] ?? array(),
				array(
					'enable'          => $is_on,
					'max-suggestions' => (string) $max,
					'custom-heading'  => $option[ $pt ]['custom-heading'] ?? '',
					'index_name'      => '' !== $base_index ? $base_index . '_' . $pt : '',
					'index_id'        => 'searchable_posts_' . $pt,
					'tmpl_suggestion' => 'autocomplete_' . $pt . '_suggestion',
				)
			);
		}

		update_option( 'wlc_algolia_settings_autocomplete', $this->sanitize_deep( $option ), false );
	}
}
