<?php

namespace GOSP\GravityOps\Core\SuiteCore\Admin;

use GOSP\GravityOps\Core\SuiteCore\SuiteCore;
use Throwable;

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

/**
 * Handles the suite-wide GravityOps survey lifecycle and interface.
 */
class SurveyPrompter {

	private const OPTION_NAME        = 'gravityops_survey_status';
	private const STATE_SCHEDULED    = 'scheduled';
	private const STATE_READY        = 'ready';
	private const STATE_COMPLETED    = 'completed';
	private const STATE_SKIPPED      = 'skipped';
	private const STATE_REMIND_LATER = 'remind_later';

	/**
	 * Legacy plugin prefixes whose survey state is migrated and removed.
	 *
	 * @var string[]
	 */
	private const LEGACY_PREFIXES = [
		'go_f4g_',
		'gosp_',
		'gvfgm_',
		'iawgf_',
		'kvfgv_',
		'menfgf_',
		'rfsfgf_',
		'gofrm_',
		'gocharts_',
		'gomm_',
		'golfv_',
		'gocsm_',
		'gomps_',
		'gowsr_',
		'goaif_',
	];

	/**
	 * SuiteCore instance.
	 *
	 * @var SuiteCore
	 */
	private SuiteCore $core;

	/**
	 * Capability required to use the survey.
	 *
	 * @var string
	 */
	private string $capability;

	/**
	 * Survey submission endpoint.
	 *
	 * @var string
	 */
	private string $endpoint;

	/**
	 * Delay before the first prompt and reminders.
	 *
	 * @var int
	 */
	private int $delay;

	/**
	 * Whether hooks have already been registered.
	 *
	 * @var bool
	 */
	private bool $did_init = false;

	/**
	 * SurveyPrompter constructor.
	 *
	 * @param SuiteCore $core       SuiteCore instance.
	 * @param string    $capability Capability required to use the survey.
	 * @param string    $endpoint   Survey submission endpoint.
	 * @param int       $delay      Delay before showing the survey.
	 */
	public function __construct( SuiteCore $core, string $capability = 'gform_full_access', string $endpoint = '', int $delay = 5 * DAY_IN_SECONDS ) {
		$this->core       = $core;
		$this->capability = $capability;
		$this->endpoint   = $endpoint ?: $core->config()->get_survey_endpoint();
		$this->delay      = $delay;
	}

	/**
	 * Registers the shared survey hooks and dashboard page.
	 *
	 * @return void
	 */
	public function init() {
		if ( $this->did_init ) {
			return;
		}

		$this->did_init = true;
		$this->migrate_legacy_state();
		$this->advance_state();

		add_action( 'admin_notices', [ $this, 'maybe_render_notice' ] );
		add_action( 'wp_ajax_gops_survey_action', [ $this, 'ajax_handle_action' ] );

		$this->core->shell()->register_plugin_page(
			'gravity_ops_survey',
			[
				'title'       => 'GravityOps Survey',
				'subtitle'    => 'Help us improve the GravityOps suite.',
				'capability'  => $this->capability,
				'hide_header' => false,
				'render'      => [ $this, 'render_page' ],
			]
		);
	}

	/**
	 * Renders the compact suite-wide survey notice.
	 *
	 * @return void
	 */
	public function maybe_render_notice() {
		if ( ! current_user_can( $this->capability ) || empty( $this->core->survey_products() ) ) {
			return;
		}

		$status = $this->get_status();
		if ( self::STATE_READY !== $status['state'] ) {
			return;
		}

		$current_page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( 'gravity_ops_survey' === $current_page ) {
			return;
		}

		$notice_classes = 'notice notice-info is-dismissible gops-feedback-notice gops-survey-notice';
		if ( 'gravity_ops' === $current_page || $this->core->shell()->has_page( $current_page ) ) {
			$notice_classes .= ' inline';
		}

		?>
		<div class="<?= esc_attr( $notice_classes ); ?>"
			data-gops-feedback-action="gops_survey_action"
			data-gops-feedback-nonce="<?= esc_attr( wp_create_nonce( 'gops_survey' ) ); ?>"
			data-gops-feedback-value="skip">
			<p>
				<strong>Would you take a one-minute survey to help improve GravityOps?</strong>
				<a class="button button-primary" href="<?= esc_url( admin_url( 'admin.php?page=gravity_ops_survey' ) ); ?>">Take the survey</a>
				<button class="button gops-survey-action" type="button" data-gops-survey-action="remind">Remind me in 5 days</button>
				<button class="button gops-survey-action" type="button" data-gops-survey-action="skip">No thanks</button>
			</p>
		</div>
		<?php
		$this->render_feedback_script();
	}

	/**
	 * Renders the dedicated GravityOps survey page.
	 *
	 * @return void
	 */
	public function render_page() {
		$status = $this->get_status();
		if ( self::STATE_COMPLETED === $status['state'] ) {
			echo '<div class="gops-card"><h2>Thank you for your feedback!</h2><p>Your response has been submitted.</p></div>';
			return;
		}

		if ( self::STATE_SKIPPED === $status['state'] ) {
			echo '<div class="gops-card"><h2>Survey declined</h2><p>You will not be asked again.</p></div>';
			return;
		}

		if ( self::STATE_READY !== $status['state'] ) {
			echo '<div class="gops-card"><h2>Survey unavailable</h2><p>The GravityOps survey is not currently available.</p></div>';
			return;
		}

		?>
		<div class="gops-card gops-survey-page" data-gops-survey-result>
			<h2>Tell us about your experience with GravityOps</h2>
			<p>This short survey covers all GravityOps tools active on this site.</p>
			<form data-gops-survey-form>
				<fieldset>
					<legend>How do you like the GravityOps tools you use?</legend>
					<?php for ( $rating = 1; $rating <= 5; $rating++ ) : ?>
						<label><input type="radio" name="rating" value="<?= esc_attr( $rating ); ?>"> <?= esc_html( str_repeat( '★', $rating ) ); ?></label>
					<?php endfor; ?>
				</fieldset>
				<p><label>What do you like most about GravityOps?<br><textarea class="large-text" rows="4" name="like"></textarea></label></p>
				<p><label>What do you like least, or where have you experienced issues, bugs, or friction?<br><textarea class="large-text" rows="4" name="dislike"></textarea></label></p>
				<fieldset>
					<legend>How likely are you to recommend GravityOps to a friend or colleague?</legend>
					<label><input type="radio" name="recommend" value="1"> Probably not</label><br>
					<label><input type="radio" name="recommend" value="2"> If it comes up</label><br>
					<label><input type="radio" name="recommend" value="3"> Very likely</label>
				</fieldset>
				<p><label>How did you first hear about GravityOps?<br><input class="regular-text" type="text" name="find"></label></p>
				<input type="hidden" name="nonce" value="<?= esc_attr( wp_create_nonce( 'gops_survey' ) ); ?>">
				<p>
					<button class="button button-primary" type="submit">Submit survey</button>
					<button class="button gops-survey-action" type="button" data-gops-survey-action="skip">No thanks</button>
				</p>
			</form>
		</div>
		<?php
		$this->render_feedback_script();
	}

	/**
	 * Handles survey actions without a full page reload.
	 *
	 * @return void
	 */
	public function ajax_handle_action() {
		if ( ! current_user_can( $this->capability ) ) {
			wp_send_json_error( [ 'message' => 'You do not have permission to update this survey.' ], 403 );
		}

		$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $nonce, 'gops_survey' ) ) {
			wp_send_json_error( [ 'message' => 'Security check failed. Refresh and try again.' ], 403 );
		}

		$action = isset( $_POST['survey_action'] ) ? sanitize_key( wp_unslash( $_POST['survey_action'] ) ) : '';
		if ( ! in_array( $action, [ 'done', 'remind', 'skip' ], true ) ) {
			wp_send_json_error( [ 'message' => 'Unknown survey action.' ], 400 );
		}

		if ( self::STATE_READY !== $this->get_status()['state'] ) {
			wp_send_json_error( [ 'message' => 'This survey is no longer available.' ], 409 );
		}

		if ( 'skip' === $action ) {
			$this->update_status( self::STATE_SKIPPED );
			wp_send_json_success( [ 'message' => 'You will not be asked again.' ] );
		}

		if ( 'remind' === $action ) {
			$this->update_status( self::STATE_REMIND_LATER, time() + $this->delay );
			wp_send_json_success( [ 'message' => 'We will remind you in five days.' ] );
		}

		$survey_data = [
			'rating'    => absint( $_POST['rating'] ?? 0 ),
			'like'      => sanitize_textarea_field( wp_unslash( $_POST['like'] ?? '' ) ),
			'dislike'   => sanitize_textarea_field( wp_unslash( $_POST['dislike'] ?? '' ) ),
			'recommend' => absint( $_POST['recommend'] ?? 0 ),
			'find'      => sanitize_text_field( wp_unslash( $_POST['find'] ?? '' ) ),
		];

		$response = wp_remote_post(
			$this->endpoint,
			[
				'method' => 'POST',
				'body'   => $this->build_submission_data( $survey_data ),
			]
		);

		if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) < 200 || wp_remote_retrieve_response_code( $response ) >= 300 ) {
			wp_send_json_error( [ 'message' => 'There was an error submitting your feedback. Please try again later.' ], 502 );
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( ! is_array( $body ) || ( isset( $body['is_valid'] ) && ! $body['is_valid'] ) ) {
			wp_send_json_error( [ 'message' => 'There was an error submitting your feedback. Please check the form and try again.' ], 422 );
		}

		$this->update_status( self::STATE_COMPLETED );
		wp_send_json_success( [ 'message' => 'Thank you so much for your feedback!' ] );
	}

	/**
	 * Builds the Gravity Forms submission payload.
	 *
	 * @param array $survey_data Sanitized survey answers.
	 *
	 * @return array<string,string>
	 */
	private function build_submission_data( array $survey_data ) {
		$names    = [];
		$plans    = [];
		$versions = [];

		foreach ( $this->core->survey_products() as $product ) {
			$title      = (string) ( $product['title'] ?? '' );
			$names[]    = $title;
			$plans[]    = $title . ': ' . $this->resolve_plan_name( $product['plan_resolver'] ?? 'unknown' );
			$versions[] = $title . ': ' . (string) ( $product['version'] ?? 'unknown' );
		}

		return [
			'input_1'  => get_site_url(),
			'input_3'  => implode( ', ', $names ),
			'input_4'  => implode( '; ', $plans ),
			'input_5'  => implode( '; ', $versions ),
			'input_14' => $this->map_rating( $survey_data['rating'] ),
			'input_7'  => $survey_data['like'],
			'input_8'  => $survey_data['dislike'],
			'input_13' => $this->map_recommendation( $survey_data['recommend'] ),
			'input_12' => $survey_data['find'],
		];
	}

	/**
	 * Prints the small shared AJAX controller once per page.
	 *
	 * @return void
	 */
	public function render_feedback_script() {
		?>
		<script>
		(function() {
			if (window.gopsFeedbackActionsBound) {
				return;
			}
			window.gopsFeedbackActionsBound = true;

			function request(data) {
				return fetch(window.ajaxurl, {
					method: 'POST',
					credentials: 'same-origin',
					headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
					body: new URLSearchParams(data).toString()
				}).then(function(response) {
					return response.json().then(function(json) {
						if (!response.ok || !json.success) {
							throw new Error(json.data && json.data.message ? json.data.message : 'The request failed.');
						}
						return json.data || {};
					});
				});
			}

			function replaceCardMessage(message) {
				var card = document.querySelector('[data-gops-survey-result]');
				if (!card) {
					return;
				}
				var heading = document.createElement('h2');
				heading.textContent = message;
				card.replaceChildren(heading);
			}

			document.addEventListener('click', function(event) {
				var dismiss = event.target.closest('.gops-feedback-notice .notice-dismiss');
				var actionButton = event.target.closest('.gops-survey-action');
				if (!dismiss && !actionButton) {
					return;
				}

				var notice = event.target.closest('.gops-feedback-notice');
				var nonceInput = document.querySelector('[data-gops-survey-form] input[name="nonce"]');
				var nonce = notice ? notice.dataset.gopsFeedbackNonce : (nonceInput ? nonceInput.value : '');
				var action = notice ? notice.dataset.gopsFeedbackAction : 'gops_survey_action';
				var value = actionButton ? actionButton.dataset.gopsSurveyAction : (notice ? notice.dataset.gopsFeedbackValue : 'skip');
				var data = {action: action, nonce: nonce || ''};
				if ('gops_survey_action' === action) {
					data.survey_action = value;
				} else if (notice) {
					data.prefix = notice.dataset.gopsFeedbackPrefix || '';
				}

				event.preventDefault();
				event.stopImmediatePropagation();
				request(data).then(function(result) {
					if (notice) {
						notice.remove();
						return;
					}
					replaceCardMessage(result.message || 'Survey updated.');
				}).catch(function(error) {
					window.alert(error.message);
				});
			}, true);

			document.addEventListener('submit', function(event) {
				var form = event.target.closest('[data-gops-survey-form]');
				if (!form) {
					return;
				}
				event.preventDefault();
				var data = Object.fromEntries(new FormData(form).entries());
				data.action = 'gops_survey_action';
				data.survey_action = 'done';
				request(data).then(function(result) {
					replaceCardMessage(result.message || 'Thank you for your feedback!');
				}).catch(function(error) {
					window.alert(error.message);
				});
			});
		})();
		</script>
		<?php
	}

	/**
	 * Migrates terminal legacy state and removes every plugin-owned survey option and cron hook.
	 *
	 * @return void
	 */
	private function migrate_legacy_state() {
		$status = get_option( self::OPTION_NAME, null );
		if ( is_array( $status ) && ! empty( $status['legacy_cleaned'] ) ) {
			return;
		}

		$completed = false;
		$skipped   = false;
		foreach ( self::LEGACY_PREFIXES as $prefix ) {
			$legacy = get_option( $prefix . 'survey_status', null );
			if ( is_array( $legacy ) ) {
				$completed = $completed || self::STATE_COMPLETED === ( $legacy['state'] ?? '' );
				$skipped   = $skipped || self::STATE_SKIPPED === ( $legacy['state'] ?? '' );
			}
			delete_option( $prefix . 'survey_status' );
			wp_clear_scheduled_hook( $prefix . 'survey' );
		}

		if ( ! is_array( $status ) ) {
			if ( $completed ) {
				$status = [
					'state'  => self::STATE_COMPLETED,
					'due_at' => 0,
				];
			} elseif ( $skipped ) {
				$status = [
					'state'  => self::STATE_SKIPPED,
					'due_at' => 0,
				];
			} else {
				$status = [
					'state'  => self::STATE_SCHEDULED,
					'due_at' => time() + $this->delay,
				];
			}
		}

		$status['legacy_cleaned'] = true;
		update_option( self::OPTION_NAME, $status );
	}

	/**
	 * Moves a due scheduled or postponed survey into the ready state.
	 *
	 * @return void
	 */
	private function advance_state() {
		$status = $this->get_status();
		if ( in_array( $status['state'], [ self::STATE_SCHEDULED, self::STATE_REMIND_LATER ], true ) && $status['due_at'] <= time() ) {
			$this->update_status( self::STATE_READY );
		}
	}

	/**
	 * Returns the normalized survey status.
	 *
	 * @return array{state:string,due_at:int}
	 */
	private function get_status() {
		$status = get_option( self::OPTION_NAME, [] );

		return [
			'state'  => isset( $status['state'] ) && is_string( $status['state'] ) ? $status['state'] : self::STATE_SCHEDULED,
			'due_at' => isset( $status['due_at'] ) ? absint( $status['due_at'] ) : 0,
		];
	}

	/**
	 * Persists a survey lifecycle state.
	 *
	 * @param string $state  Lifecycle state.
	 * @param int    $due_at Timestamp at which a delayed state becomes ready.
	 *
	 * @return void
	 */
	private function update_status( $state, $due_at = 0 ) {
		update_option(
			self::OPTION_NAME,
			[
				'state'          => $state,
				'due_at'         => absint( $due_at ),
				'legacy_cleaned' => true,
			]
		);
	}

	/**
	 * Resolves a product plan name.
	 *
	 * @param mixed $resolver Plan resolver or plan value.
	 *
	 * @return string
	 */
	private function resolve_plan_name( $resolver ) {
		if ( is_string( $resolver ) ) {
			return $resolver;
		}
		if ( is_object( $resolver ) && isset( $resolver->name ) ) {
			return (string) $resolver->name;
		}

		if ( is_callable( $resolver ) ) {
			try {
				$plan = call_user_func( $resolver );
				if ( is_object( $plan ) && isset( $plan->name ) ) {
					return (string) $plan->name;
				}
				if ( is_string( $plan ) ) {
					return $plan;
				}
			} catch ( Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
				// Use the fallback below if a licensing integration is unavailable.
			}
		}

		return 'unknown';
	}

	/**
	 * Maps a numeric rating to the form's star value.
	 *
	 * @param int $rating Rating value.
	 *
	 * @return string
	 */
	private function map_rating( $rating ) {
		return $rating >= 1 && $rating <= 5 ? str_repeat( '⭐', $rating ) : '';
	}

	/**
	 * Maps a recommendation choice to the form's stored value.
	 *
	 * @param int $recommend Recommendation value.
	 *
	 * @return string
	 */
	private function map_recommendation( $recommend ) {
		$choices = [
			1 => 'Probably not',
			2 => 'If it comes up',
			3 => 'Very likely',
		];

		return $choices[ $recommend ] ?? '';
	}
}
